@adimm/x-injection-reactjs 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +924 -196
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -12,41 +12,134 @@
12
12
  <a href="https://www.npmjs.com/package/@adimm/x-injection-reactjs" target="__blank"><img src="https://badgen.net/npm/dm/@adimm/x-injection-reactjs"></a>
13
13
  </p>
14
14
 
15
- **Powerful dependency injection for React components using a modular architecture. Build scalable React applications with clean separation of concerns.** _(Inspired by Angular and NestJS IoC/DI)_
15
+ **Stop wrestling with React Context and prop drilling. Build scalable React apps with clean, testable business logic separated from UI.**
16
+
17
+ > **TL;DR** — Mark classes with `@Injectable()`, declare a `ProviderModule.blueprint()`, wrap your component with `provideModuleToComponent(MyModuleBp, () => { ... })`, then call `useInject(MyService)` inside. Dependencies are resolved automatically — no providers, no prop drilling, no manual wiring.
16
18
 
17
19
  ## Table of Contents
18
20
 
19
21
  - [Table of Contents](#table-of-contents)
20
- - [Overview](#overview)
22
+ - [What Problems Does This Solve?](#what-problems-does-this-solve)
23
+ - [1. Provider Hell](#1-provider-hell)
24
+ - [2. Prop Drilling](#2-prop-drilling)
25
+ - [3. Manual Dependency Wiring](#3-manual-dependency-wiring)
26
+ - [4. Business Logic Mixed with UI](#4-business-logic-mixed-with-ui)
21
27
  - [Installation](#installation)
22
28
  - [Quick Start](#quick-start)
23
- - [The Problem](#the-problem)
24
- - [Without xInjection](#without-xinjection)
25
- - [With xInjection](#with-xinjection)
26
- - [Core Concepts](#core-concepts)
27
- - [Component Modules](#component-modules)
28
- - [Services](#services)
29
- - [Dependency Injection](#dependency-injection)
30
- - [Custom Hooks](#custom-hooks)
31
- - [Examples](#examples)
32
- - [Zustand Integration](#zustand-integration)
33
- - [Parent-Child Provider Control](#parent-child-provider-control)
34
- - [Advanced Usage](#advanced-usage)
29
+ - [How It Works](#how-it-works)
30
+ - [1. Services: Your Business Logic](#1-services-your-business-logic)
31
+ - [2. Modules: Organizing Dependencies](#2-modules-organizing-dependencies)
32
+ - [3. Injecting Services into Components](#3-injecting-services-into-components)
33
+ - [The Power of Component-Scoped Modules](#the-power-of-component-scoped-modules)
34
+ - [What Are Component-Scoped Modules?](#what-are-component-scoped-modules)
35
+ - [Pattern 1: Multiple Independent Instances](#pattern-1-multiple-independent-instances)
36
+ - [Pattern 2: Parent-Child Dependency Control](#pattern-2-parent-child-dependency-control)
37
+ - [Why Use the HoC Approach?](#why-use-the-hoc-approach)
38
+ - [1. Lifecycle-Bound Isolated Containers](#1-lifecycle-bound-isolated-containers)
39
+ - [2. Composition and Reusability](#2-composition-and-reusability)
40
+ - [Hierarchical Dependency Injection](#hierarchical-dependency-injection)
41
+ - [Creating Custom Hooks with Dependencies](#creating-custom-hooks-with-dependencies)
42
+ - [Parent Components Controlling Child Dependencies](#parent-components-controlling-child-dependencies)
35
43
  - [Module Imports and Exports](#module-imports-and-exports)
36
- - [Multiple Dependency Injection](#multiple-dependency-injection)
37
- - [Unit Testing](#unit-testing)
38
- - [Documentation](#documentation)
44
+ - [Real-World Examples](#real-world-examples)
45
+ - [Zustand Store Integration](#zustand-store-integration)
46
+ - [Complex Form with Shared State](#complex-form-with-shared-state)
47
+ - [Testing Your Code](#testing-your-code)
48
+ - [Mocking an Entire Module](#mocking-an-entire-module)
49
+ - [Mocking on-the-fly](#mocking-on-the-fly)
50
+ - [FAQ](#faq)
51
+ - [How do I add global services?](#how-do-i-add-global-services)
52
+ - [When should I use global modules vs component-scoped modules?](#when-should-i-use-global-modules-vs-component-scoped-modules)
53
+ - [Can I use this with Redux/MobX/Zustand?](#can-i-use-this-with-reduxmobxzustand)
54
+ - [How does this compare to React Context?](#how-does-this-compare-to-react-context)
55
+ - [If I want Angular patterns, why not just use Angular?](#if-i-want-angular-patterns-why-not-just-use-angular)
56
+ - [Can I migrate gradually from an existing React app?](#can-i-migrate-gradually-from-an-existing-react-app)
57
+ - [When do I actually need `provideModuleToComponent`?](#when-do-i-actually-need-providemoduletocomponent)
58
+ - [What's the performance impact?](#whats-the-performance-impact)
59
+ - [Why use classes for services instead of custom hooks?](#why-use-classes-for-services-instead-of-custom-hooks)
60
+ - [Links](#links)
39
61
  - [Contributing](#contributing)
40
62
  - [License](#license)
41
63
 
42
- ## Overview
64
+ ## What Problems Does This Solve?
65
+
66
+ If you've built React apps, you've probably encountered these pain points:
67
+
68
+ ### 1. Provider Hell
69
+
70
+ Your `App.tsx` becomes a nightmare of nested providers:
71
+
72
+ ```tsx
73
+ <AuthProvider>
74
+ <ThemeProvider>
75
+ <ApiProvider>
76
+ <ToastProvider>
77
+ <UserProvider>
78
+ <App />
79
+ </UserProvider>
80
+ </ToastProvider>
81
+ </ApiProvider>
82
+ </ThemeProvider>
83
+ </AuthProvider>
84
+ ```
85
+
86
+ ### 2. Prop Drilling
87
+
88
+ You pass props through 5 levels of components just to reach the one that needs them:
89
+
90
+ ```tsx
91
+ <Dashboard user={user}>
92
+ <Sidebar user={user}>
93
+ <UserMenu user={user}>
94
+ <UserAvatar user={user} /> {/* Finally! */}
95
+ </UserMenu>
96
+ </Sidebar>
97
+ </Dashboard>
98
+ ```
99
+
100
+ ### 3. Manual Dependency Wiring
101
+
102
+ When a service needs dependencies, you manually create them in the right order:
103
+
104
+ ```tsx
105
+ function UserProfile() {
106
+ // Must create ALL dependencies manually in correct order
107
+ const toastService = new ToastService();
108
+ const apiService = new ApiService();
109
+ const authService = new AuthService(apiService);
110
+ const userProfileService = new UserProfileService(apiService, authService, toastService);
111
+
112
+ // If AuthService adds a new dependency tomorrow, THIS BREAKS!
113
+ return <div>{userProfileService.displayName}</div>;
114
+ }
115
+ ```
116
+
117
+ ### 4. Business Logic Mixed with UI
118
+
119
+ Your components become bloated with API calls, state management, and validation:
43
120
 
44
- xInjection for React brings dependency injection to your React components, enabling:
121
+ ```tsx
122
+ function UserDashboard() {
123
+ const [user, setUser] = useState(null);
124
+ const [loading, setLoading] = useState(false);
125
+
126
+ useEffect(() => {
127
+ setLoading(true);
128
+ fetch('/api/user')
129
+ .then((res) => res.json())
130
+ .then((data) => {
131
+ setUser(data);
132
+ setLoading(false);
133
+ });
134
+ }, []);
135
+
136
+ // 50 more lines of business logic...
137
+
138
+ return <div>{/* Your actual UI */}</div>;
139
+ }
140
+ ```
45
141
 
46
- - **Service-based architecture**: Separate business logic from UI components
47
- - **Modular design**: Create reusable, testable component modules
48
- - **State management integration**: Works seamlessly with Zustand, Redux, or any state library
49
- - **Parent-child provider control**: Parent components can control child component dependencies
142
+ xInjection solves all of the above by bringing **Inversion of Control (IoC)** and **Dependency Injection (DI)** to React: instead of components creating and managing their own dependencies, they just ask for what they need and xInjection provides it — automatically, type-safely, and testably.
50
143
 
51
144
  This is the official [ReactJS](https://react.dev/) implementation of [xInjection](https://github.com/AdiMarianMutu/x-injection).
52
145
 
@@ -56,6 +149,21 @@ This is the official [ReactJS](https://react.dev/) implementation of [xInjection
56
149
  npm i @adimm/x-injection-reactjs reflect-metadata
57
150
  ```
58
151
 
152
+ > [!IMPORTANT]
153
+ > Import `reflect-metadata` at the very top of your app entry point:
154
+
155
+ ```tsx
156
+ // main.tsx or index.tsx
157
+
158
+ import 'reflect-metadata';
159
+
160
+ import { createRoot } from 'react-dom/client';
161
+
162
+ import App from './App';
163
+
164
+ createRoot(document.getElementById('root')!).render(<App />);
165
+ ```
166
+
59
167
  **TypeScript Configuration**
60
168
 
61
169
  Add to your `tsconfig.json`:
@@ -69,171 +177,605 @@ Add to your `tsconfig.json`:
69
177
  }
70
178
  ```
71
179
 
180
+ > **📚 Advanced Concepts**
181
+ >
182
+ > This documentation covers React-specific usage patterns. For advanced features like **lifecycle hooks** (`onReady`, `onDispose`), **injection scopes** (Singleton, Transient, Request), **middlewares**, **events**, and **dynamic module updates**, refer to the [base xInjection library documentation](https://github.com/AdiMarianMutu/x-injection).
183
+ >
184
+ > The base library provides the core IoC/DI engine that powers this React integration.
185
+
72
186
  ## Quick Start
73
187
 
188
+ Three files, three concepts: global services declared once, a component-scoped module, and a component that injects both.
189
+
190
+ **Step 1 — Declare global services** in your entry point:
191
+
74
192
  ```tsx
75
- import { Injectable, provideModuleToComponent, ProviderModule, useInject } from '@adimm/x-injection-reactjs';
193
+ // main.tsx - Your app entry point
194
+
195
+ import 'reflect-metadata';
196
+
197
+ import { Injectable, ProviderModule } from '@adimm/x-injection';
198
+ import { createRoot } from 'react-dom/client';
199
+
200
+ import App from './App';
76
201
 
77
- // 1. Define a service
202
+ // Global services (singletons)
78
203
  @Injectable()
79
- class UserService {
80
- firstName = 'John';
81
- lastName = 'Doe';
204
+ class ApiService {
205
+ get(url: string) {
206
+ return fetch(url).then((r) => r.json());
207
+ }
82
208
  }
83
209
 
84
- // 2. Create a module blueprint
210
+ @Injectable()
211
+ class AuthService {
212
+ constructor(private readonly apiService: ApiService) {}
213
+
214
+ isLoggedIn = false;
215
+
216
+ login() {
217
+ this.isLoggedIn = true;
218
+ }
219
+ }
220
+
221
+ // Create global module - automatically imported into built-in AppModule
222
+ ProviderModule.blueprint({
223
+ id: 'AppBootstrapModule',
224
+ isGlobal: true,
225
+ providers: [ApiService, AuthService],
226
+ exports: [ApiService, AuthService], // Exported services available everywhere
227
+ });
228
+
229
+ // Now render your app
230
+ createRoot(document.getElementById('root')!).render(<App />);
231
+ ```
232
+
233
+ **Step 2 — Create a component-scoped module and inject services:**
234
+
235
+ ```tsx
236
+ // UserDashboard.tsx - A component with its own service
237
+
238
+ import { Injectable, ProviderModule } from '@adimm/x-injection';
239
+ import { provideModuleToComponent, useInject } from '@adimm/x-injection-reactjs';
240
+
241
+ // Component-scoped service
242
+ @Injectable()
243
+ class UserDashboardService {
244
+ constructor(private readonly apiService: ApiService) {} // Gets global ApiService
245
+
246
+ async loadUser() {
247
+ return this.apiService.get('/user');
248
+ }
249
+ }
250
+
251
+ // Component-scoped module
85
252
  const UserDashboardModuleBp = ProviderModule.blueprint({
86
253
  id: 'UserDashboardModule',
87
- providers: [UserService],
254
+ providers: [UserDashboardService],
88
255
  });
89
256
 
90
- // 3. Create a component with dependency injection
91
- const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
92
- const userService = useInject(UserService);
257
+ // Component with injected service
258
+ export const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
259
+ const dashboardService = useInject(UserDashboardService);
260
+ const authService = useInject(AuthService); // Can also inject global services
93
261
 
94
262
  return (
95
- <h1>
96
- Hello {userService.firstName} {userService.lastName}!
97
- </h1>
263
+ <div>
264
+ <h1>Dashboard</h1>
265
+ <p>Logged in: {authService.isLoggedIn ? 'Yes' : 'No'}</p>
266
+ </div>
98
267
  );
99
268
  });
100
269
  ```
101
270
 
102
- ## The Problem
271
+ **Step 3 — Use the component** — each instance gets its own module:
103
272
 
104
- React apps often suffer from **provider hell**, **prop drilling**, and **manual dependency wiring**:
273
+ ```tsx
274
+ // App.tsx
105
275
 
106
- ### Without xInjection
276
+ import { UserDashboard } from './UserDashboard';
277
+
278
+ export default function App() {
279
+ return (
280
+ <div>
281
+ <UserDashboard />
282
+ <UserDashboard /> {/* Each gets its own UserDashboardService */}
283
+ </div>
284
+ );
285
+ }
286
+ ```
287
+
288
+ > [!TIP] Global vs component-scoped services:
289
+ >
290
+ > - Global services (`ApiService`, `AuthService`): Defined in a global blueprint, automatically imported into the built-in `AppModule`
291
+ > - Component-scoped services (`UserDashboardService`): Fresh instance per `<UserDashboard />`
292
+ > - Component-scoped services can inject global services automatically
293
+
294
+ ## How It Works
295
+
296
+ Let's break down the three main concepts you'll use:
297
+
298
+ ### 1. Services: Your Business Logic
299
+
300
+ A **service** is just a class that contains your business logic. Think of it as extracting all the "smart stuff" from your component into a reusable, testable class.
107
301
 
108
302
  ```tsx
109
- // Problem 1: Provider Hell
110
- <AuthProvider>
111
- <ApiProvider>
112
- <ToastProvider>
113
- <App />
114
- </ToastProvider>
115
- </ApiProvider>
116
- </AuthProvider>;
117
-
118
- // Problem 2: Manual Dependency Wiring
119
- function UserProfile() {
120
- // Must manually create ALL dependencies in correct order
121
- const toast = new ToastService();
122
- const api = new ApiService();
123
- const auth = new AuthService(api);
124
- const userProfile = new UserProfileService(api, auth, toast);
125
-
126
- // If AuthService adds a dependency, ALL consumers break!
127
- return <div>{userProfile.displayName}</div>;
303
+ @Injectable()
304
+ class TodoService {
305
+ private todos: Todo[] = [];
306
+
307
+ addTodo(text: string) {
308
+ this.todos.push({ id: Date.now(), text, completed: false });
309
+ }
310
+
311
+ getTodos() {
312
+ return this.todos;
313
+ }
314
+
315
+ toggleTodo(id: number) {
316
+ const todo = this.todos.find((t) => t.id === id);
317
+ if (todo) todo.completed = !todo.completed;
318
+ }
128
319
  }
129
320
  ```
130
321
 
131
- ### With xInjection
322
+ The `@Injectable()` decorator marks this class as something that can be injected (either into components or other services/modules).
323
+
324
+ **Services can depend on other services:**
132
325
 
133
326
  ```tsx
134
- // 1. Define global services (shared across all components) - Usually in your app entrypoint/bootstrap file.
135
- const AppModuleBp = ProviderModule.blueprint({
136
- id: 'AppModule',
137
- isGlobal: true, // Available everywhere, only created once
138
- providers: [ToastService, ApiService, AuthService],
327
+ @Injectable()
328
+ class UserProfileService {
329
+ // Dependencies are automatically injected via constructor
330
+ constructor(
331
+ private readonly apiService: ApiService,
332
+ private readonly authService: AuthService,
333
+ private readonly toastService: ToastService
334
+ ) {}
335
+
336
+ async loadProfile() {
337
+ try {
338
+ const userId = this.authService.getCurrentUserId();
339
+ const profile = await this.apiService.get(`/users/${userId}`);
340
+ return profile;
341
+ } catch (error) {
342
+ this.toastService.error('Failed to load profile');
343
+ throw error;
344
+ }
345
+ }
346
+ }
347
+ ```
348
+
349
+ Notice how `UserProfileService` asks for its dependencies in the constructor? xInjection will automatically provide them.
350
+
351
+ **Alternative: Property Injection**
352
+
353
+ You can also use the `@Inject` decorator from the base library for property injection:
354
+
355
+ ```tsx
356
+ import { Inject, Injectable } from '@adimm/x-injection';
357
+
358
+ @Injectable()
359
+ class UserProfileService {
360
+ @Inject(ApiService)
361
+ private readonly apiService!: ApiService;
362
+
363
+ @Inject(AuthService)
364
+ private readonly authService!: AuthService;
365
+
366
+ async loadProfile() {
367
+ const userId = this.authService.getCurrentUserId();
368
+ return this.apiService.get(`/users/${userId}`);
369
+ }
370
+ }
371
+ ```
372
+
373
+ Both approaches work! Constructor injection is generally preferred for better type safety and easier testing.
374
+
375
+ ### 2. Modules: Organizing Dependencies
376
+
377
+ A **module** is a container that tells xInjection which services are available. Think of it as a "package" of services.
378
+
379
+ **Modules come in two flavors:**
380
+
381
+ ```tsx
382
+ // Global module: Created once, shared everywhere
383
+ ProviderModule.blueprint({
384
+ id: 'AppBootstrapModule',
385
+ isGlobal: true,
386
+ providers: [ApiService, AuthService, ToastService],
387
+ exports: [ApiService, AuthService, ToastService], // Only exported services become globally available
139
388
  });
140
389
 
141
- // 2. Define component-specific services - Per component
142
- const UserProfileModuleBp = ProviderModule.blueprint({
143
- id: 'UserProfileModule',
144
- providers: [UserProfileService], // Automatically gets ApiService, AuthService, ToastService
390
+ // Component-scoped module: Each component instance gets its own
391
+ const TodoListModuleBp = ProviderModule.blueprint({
392
+ id: 'TodoListModule',
393
+ providers: [TodoService], // Gets a fresh TodoService per component
145
394
  });
395
+ ```
396
+
397
+ > [!IMPORTANT]
398
+ > When using `isGlobal: true`, only services listed in the `exports` array become globally available. Non-exported providers remain private to the module.
399
+
400
+ > [!CAUTION] Global modules cannot be used with `provideModuleToComponent`
401
+ > Attempting to provide a global module to a component will throw an `InjectionProviderModuleError`. Global services are accessed directly via `useInject` without the HoC.
402
+
403
+ **`blueprint()` vs `create()`:**
404
+
405
+ - **`blueprint()`**: A deferred module template. Each time it is imported or used with `provideModuleToComponent`, a **new independent instance** is created. Use for the global bootstrap module and for component-scoped modules. [Learn more](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#blueprints).
406
+ - **`create()`**: Immediately instantiates a module. The resulting instance is a **single shared object** — every module that imports it shares the exact same instance. Use when you need a module that is instantiated once and shared across multiple other modules.
407
+
408
+ See [Module Imports and Exports](#module-imports-and-exports) for examples of both.
146
409
 
147
- const UserProfile = provideModuleToComponent(UserProfileModuleBp, () => {
148
- const userProfile = useInject(UserProfileService);
149
- // IoC automatically injects: ToastService → ApiService → AuthService → UserProfileService
150
- return <div>{userProfile.displayName}</div>;
410
+ > [!CAUTION] Never import `AppModule` into other modules
411
+ > `AppModule` is the built-in global container and importing it will throw an error. Use global blueprints with `isGlobal: true` instead, which are automatically imported into `AppModule`.
412
+
413
+ ### 3. Injecting Services into Components
414
+
415
+ Use the `provideModuleToComponent` Higher-Order Component (HoC) to give your component access to services:
416
+
417
+ ```tsx
418
+ const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
419
+ // Inject the service you need
420
+ const userProfileService = useInject(UserProfileService);
421
+
422
+ return <div>{userProfileService.displayName}</div>;
151
423
  });
152
424
  ```
153
425
 
154
- **What You Get:**
426
+ The HoC does two things:
155
427
 
156
- - **No Provider Hell** - One module replaces nested providers
157
- - **Auto Dependency Resolution** - IoC wires everything automatically
158
- - **Easy Refactoring** - Add/remove dependencies without breaking consumers
159
- - **Clean Separation** - Business logic in services, UI in components
160
- - **Fully Testable** - Mock modules or individual services
161
- - **Type-Safe** - Full TypeScript support
428
+ 1. Creates an instance of your module (and all its services)
429
+ 2. Makes those services available via the `useInject` hook
162
430
 
163
- ## Core Concepts
431
+ **You can also inject multiple services at once:**
164
432
 
165
- ### Component Modules
433
+ ```tsx
434
+ const MyComponent = provideModuleToComponent(MyModuleBp, () => {
435
+ const [userService, apiService] = useInjectMany(UserService, ApiService);
166
436
 
167
- Create a module blueprint that defines your component's dependencies:
437
+ // Use your services...
438
+ });
439
+ ```
168
440
 
169
- ```ts
170
- // user-dashboard.module.ts
171
- export const UserDashboardModuleBp = ProviderModule.blueprint({
172
- id: 'UserDashboardModule',
173
- providers: [UserService],
174
- exports: [UserService],
441
+ ## The Power of Component-Scoped Modules
442
+
443
+ One of the most powerful features of xInjection is **component-scoped modules**. This is something you can't easily achieve with React Context alone.
444
+
445
+ ### What Are Component-Scoped Modules?
446
+
447
+ When you use `provideModuleToComponent`, each instance of your component gets its **own copy** of the module and all its services. This enables powerful patterns:
448
+
449
+ ### Pattern 1: Multiple Independent Instances
450
+
451
+ ```tsx
452
+ @Injectable()
453
+ class CounterService {
454
+ count = 0;
455
+ increment() {
456
+ this.count++;
457
+ }
458
+ }
459
+
460
+ const CounterModuleBp = ProviderModule.blueprint({
461
+ id: 'CounterModule',
462
+ providers: [CounterService],
463
+ });
464
+
465
+ const Counter = provideModuleToComponent(CounterModuleBp, () => {
466
+ const counterService = useInject(CounterService);
467
+ return (
468
+ <div>
469
+ <p>Count: {counterService.count}</p>
470
+ <button onClick={() => counterService.increment()}>+</button>
471
+ </div>
472
+ );
175
473
  });
474
+
475
+ function App() {
476
+ return (
477
+ <div>
478
+ <Counter /> {/* Count: 0 */}
479
+ <Counter /> {/* Count: 0 (separate instance!) */}
480
+ </div>
481
+ );
482
+ }
176
483
  ```
177
484
 
178
- **Blueprint vs Module:** Use a **blueprint** for reusable components (multiple instances), use a raw **module** for singleton components (single instance).
485
+ Each `<Counter />` has its own `CounterService`, so they don't interfere with each other.
179
486
 
180
- ### Services
487
+ ### Pattern 2: Parent-Child Dependency Control
181
488
 
182
- Define services using the `@Injectable()` decorator:
489
+ Parent components can "inject" specific service instances into their children:
183
490
 
184
- ```ts
185
- // user-dashboard.service.ts
491
+ ```tsx
492
+ const ParentModuleBp = ProviderModule.blueprint({
493
+ id: 'ParentModule',
494
+ providers: [SharedService, ParentService],
495
+ });
496
+
497
+ const ChildModuleBp = ProviderModule.blueprint({
498
+ id: 'ChildModule',
499
+ providers: [SharedService, ChildService],
500
+ });
501
+
502
+ const Child = provideModuleToComponent(ChildModuleBp, () => {
503
+ const sharedService = useInject(SharedService);
504
+ return <div>{sharedService.data}</div>;
505
+ });
506
+
507
+ const Parent = provideModuleToComponent(ParentModuleBp, () => {
508
+ const sharedService = useInject(SharedService);
509
+
510
+ // Pass the parent's SharedService instance to the child
511
+ return <Child inject={[{ provide: SharedService, useValue: sharedService }]} />;
512
+ });
513
+ ```
514
+
515
+ This enables complex patterns like form components sharing validation services, or composite UI components coordinating state.
516
+
517
+ ## Why Use the HoC Approach?
518
+
519
+ You might wonder: "Why wrap my component with `provideModuleToComponent` instead of just using `useInject` directly everywhere?"
520
+
521
+ **Short answer:** You don't always need it! If you only use global services, you can just call `useInject` anywhere. But for **component-scoped modules** (where each component instance needs its own services), you need `provideModuleToComponent`.
522
+
523
+ The Higher-Order Component (HoC) pattern provides several key benefits:
524
+
525
+ ### 1. Lifecycle-Bound Isolated Containers
526
+
527
+ Each wrapped component gets its **own** dependency container, created on mount and disposed on unmount. Two instances of `<TodoList />` each get their own `TodoService` — they never share state. When the component unmounts, `onDispose` runs automatically, cleaning up only that component's services. Imported global services remain unaffected.
528
+
529
+ ### 2. Composition and Reusability
530
+
531
+ The HoC pattern works seamlessly with React's component composition model:
532
+
533
+ ```tsx
534
+ // Reusable component with its own dependencies
535
+ const TodoList = provideModuleToComponent(TodoListModuleBp, () => {
536
+ const todoService = useInject(TodoService);
537
+ // ...
538
+ });
539
+
540
+ // Use it multiple times, each with isolated state
541
+ function App() {
542
+ return (
543
+ <>
544
+ <TodoList /> {/* Gets its own TodoService */}
545
+ <TodoList /> {/* Gets a different TodoService */}
546
+ </>
547
+ );
548
+ }
549
+ ```
550
+
551
+ ## Hierarchical Dependency Injection
552
+
553
+ Every component wrapped with `provideModuleToComponent` gets its own module container. When `useInject` is called inside that component, xInjection walks a well-defined lookup chain:
554
+
555
+ 1. **Own module** — services declared in the component's own blueprint
556
+ 2. **Imported modules** — exported services from modules listed in `imports`
557
+ 3. **AppModule** — globally available services (from `isGlobal: true` blueprints)
558
+
559
+ ```
560
+ useInject(SomeService) ← called inside <MyComponent />
561
+
562
+
563
+ ┌─────────────────────────┐
564
+ │ MyComponent's module │ ← providers: [MyService, ...]
565
+ │ (own container) │
566
+ └───────────┬─────────────┘
567
+ │ not found
568
+
569
+ ┌─────────────────────────┐
570
+ │ Imported modules │ ← imports: [SharedModule]
571
+ │ (exported only) │ SharedModule.exports: [SharedService]
572
+ └───────────┬─────────────┘
573
+ │ not found
574
+
575
+ ┌─────────────────────────┐
576
+ │ AppModule │ ← AppBootstrapModule { isGlobal: true }
577
+ │ (global services) │ exports: [ApiService, AuthService, ...]
578
+ └───────────┬─────────────┘
579
+ │ not found
580
+
581
+ throws error
582
+ ```
583
+
584
+ **Component example:**
585
+
586
+ ```tsx
587
+ // ① Global services — live in AppModule, available everywhere
186
588
  @Injectable()
187
- export class UserDashboardService {
188
- firstName: string;
189
- lastName: string;
589
+ class ApiService {}
590
+ @Injectable()
591
+ class AuthService {}
190
592
 
191
- getFullName() {
192
- return `${this.firstName} ${this.lastName}`;
193
- }
593
+ ProviderModule.blueprint({
594
+ id: 'AppBootstrapModule',
595
+ isGlobal: true,
596
+ providers: [ApiService, AuthService],
597
+ exports: [ApiService, AuthService],
598
+ });
599
+
600
+ // ② Shared module — created once, imported into component blueprints
601
+ @Injectable()
602
+ class AnalyticsService {}
603
+
604
+ const SharedModule = ProviderModule.create({
605
+ id: 'SharedModule',
606
+ providers: [AnalyticsService],
607
+ exports: [AnalyticsService], // ✅ visible to importers
608
+ });
609
+
610
+ // ③ Component-scoped service — private to this component
611
+ @Injectable()
612
+ class DashboardService {
613
+ constructor(
614
+ private readonly api: ApiService, // resolved from ③ AppModule
615
+ private readonly analytics: AnalyticsService // resolved from ② SharedModule
616
+ ) {}
194
617
  }
618
+
619
+ const DashboardModuleBp = ProviderModule.blueprint({
620
+ id: 'DashboardModule',
621
+ imports: [SharedModule],
622
+ providers: [DashboardService], // ① own container
623
+ });
624
+
625
+ const Dashboard = provideModuleToComponent(DashboardModuleBp, () => {
626
+ const dashboard = useInject(DashboardService); // ✅ ① own module
627
+ const analytics = useInject(AnalyticsService); // ✅ ② SharedModule export
628
+ const auth = useInject(AuthService); // ✅ ③ AppModule (global)
629
+
630
+ // useInject(SomePrivateService) // ❌ not found → error
631
+ });
195
632
  ```
196
633
 
197
- ### Dependency Injection
634
+ > [!TIP]
635
+ > A service that is not listed in a module's `exports` is completely invisible to any component that imports that module. This is how xInjection enforces encapsulation — only what you explicitly export crosses the module boundary.
198
636
 
199
- Use `useInject` to access services in your components:
637
+ ### Creating Custom Hooks with Dependencies
638
+
639
+ The `hookFactory` function lets you create reusable custom hooks that automatically receive injected dependencies:
200
640
 
201
641
  ```tsx
202
- const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
203
- const userService = useInject(UserService);
642
+ // Define a custom hook with dependencies
643
+ const useUserProfile = hookFactory({
644
+ use: ({ userId, deps: [apiService, authService] }) => {
645
+ const [profile, setProfile] = useState(null);
204
646
 
205
- return <div>{userService.getFullName()}</div>;
647
+ useEffect(() => {
648
+ apiService.get(`/users/${userId}`).then(setProfile);
649
+ }, [userId]);
650
+
651
+ return profile;
652
+ },
653
+ inject: [ApiService, AuthService],
654
+ });
655
+
656
+ // Use it in any component
657
+ const UserProfile = provideModuleToComponent<{ userId: number }>(UserModuleBp, ({ userId }) => {
658
+ const profile = useUserProfile({ userId });
659
+ return <div>{profile?.name}</div>;
206
660
  });
207
661
  ```
208
662
 
209
- ### Custom Hooks
663
+ **Type-safe hooks with `HookWithDeps`:**
210
664
 
211
- Create reusable hooks with dependency injection using `hookFactory`:
665
+ Use the `HookWithDeps<P, D>` type utility for full TypeScript support:
212
666
 
213
- ```ts
214
- const useUserFullName = hookFactory({
215
- use: ({ firstName, lastName, deps: [userService] }) => {
216
- userService.firstName = firstName;
217
- userService.lastName = lastName;
218
- return userService.getFullName();
667
+ ```tsx
668
+ import type { HookWithDeps } from '@adimm/x-injection-reactjs';
669
+
670
+ // Hook with no parameters - use void as first generic
671
+ const useTestHook = hookFactory({
672
+ use: ({ deps: [testService] }: HookWithDeps<void, [TestService]>) => {
673
+ return testService.value;
674
+ },
675
+ inject: [TestService],
676
+ });
677
+
678
+ // Hook with parameters - specify parameter type as first generic
679
+ const useUserData = hookFactory({
680
+ use: ({ userId, deps: [apiService] }: HookWithDeps<{ userId: number }, [ApiService]>) => {
681
+ const [data, setData] = useState(null);
682
+ useEffect(() => {
683
+ apiService.get(`/users/${userId}`).then(setData);
684
+ }, [userId]);
685
+ return data;
219
686
  },
220
- inject: [UserService],
687
+ inject: [ApiService],
221
688
  });
222
689
 
223
- // Use in any component
224
- const fullName = useUserFullName({ firstName: 'John', lastName: 'Doe' });
690
+ // Usage:
691
+ useTestHook(); // No parameters
692
+ useUserData({ userId: 123 }); // With parameters
225
693
  ```
226
694
 
227
- ## Examples
695
+ **`HookWithDeps<P, D>` generics:**
696
+
697
+ - **`P`**: Hook parameter type (use `void` if no parameters, or `{ param1: type, ... }` for parameters)
698
+ - **`D`**: Tuple type matching your `inject` array (e.g., `[ApiService, AuthService]`)
699
+
700
+ > [!TIP] Why use hookFactory?
701
+ >
702
+ > - Dependencies are automatically injected
703
+ > - Hooks are reusable across components
704
+ > - Type-safe with TypeScript
705
+ > - Easier to test (mock dependencies)
706
+
707
+ ### Parent Components Controlling Child Dependencies
708
+
709
+ The `inject` prop allows parent components to override child component dependencies. See [Pattern 2](#pattern-2-parent-child-dependency-control) for a basic example and the [Complex Form example](#complex-form-with-shared-state) for a real-world use case.
710
+
711
+ ### Module Imports and Exports
712
+
713
+ Modules can import other modules. The key question is: **should the imported module be shared or duplicated per component?**
714
+
715
+ **Shared module instance → `ProviderModule.create()`:**
716
+
717
+ Use `create()` when a module should exist as one instance and be shared by all blueprints that import it:
718
+
719
+ ```tsx
720
+ // Instantiated once — all importers share the same instance and the same singletons
721
+ const CoreModule = ProviderModule.create({
722
+ id: 'CoreModule',
723
+ providers: [SomeSharedService],
724
+ exports: [SomeSharedService],
725
+ });
228
726
 
229
- ### Zustand Integration
727
+ const UserModuleBp = ProviderModule.blueprint({
728
+ id: 'UserModule',
729
+ imports: [CoreModule], // every <UserComponent /> shares the same CoreModule
730
+ providers: [UserService],
731
+ });
732
+
733
+ const ProductModuleBp = ProviderModule.blueprint({
734
+ id: 'ProductModule',
735
+ imports: [CoreModule], // same CoreModule instance
736
+ providers: [ProductService],
737
+ });
738
+ ```
230
739
 
231
- This example shows how to integrate Zustand store within a service, allowing the service to manipulate the store while components only subscribe to state changes.
740
+ **Per-component isolation blueprint imports:**
741
+
742
+ Import a blueprint when each component instance should get its own independent copy of those providers:
743
+
744
+ ```tsx
745
+ const UserModuleBp = ProviderModule.blueprint({
746
+ id: 'UserModule',
747
+ imports: [FormValidationModuleBp], // each <UserComponent /> gets its own FormValidationService
748
+ providers: [UserService],
749
+ });
750
+ ```
751
+
752
+ **Re-exporting:**
753
+
754
+ ```tsx
755
+ const CoreModule = ProviderModule.create({
756
+ id: 'CoreModule',
757
+ imports: [DatabaseModule, CacheModule],
758
+ exports: [DatabaseModule, CacheModule], // expose both to importers
759
+ });
760
+ ```
761
+
762
+ ## Real-World Examples
763
+
764
+ ### Zustand Store Integration
765
+
766
+ xInjection works beautifully with Zustand. The pattern is simple: **encapsulate the Zustand store inside a service**. This keeps your business logic in services while using Zustand for reactive state.
767
+
768
+ **Why this pattern?**
769
+
770
+ - Business logic stays in services (testable, reusable)
771
+ - Components subscribe to state reactively (optimal re-renders)
772
+ - Store is scoped to the component (no global state pollution)
773
+ - Type-safe and easy to test
232
774
 
233
775
  ```ts
234
776
  // counter.service.ts
235
777
 
236
- import { Injectable } from '@adimm/x-injection-reactjs';
778
+ import { Injectable } from '@adimm/x-injection';
237
779
  import { create } from 'zustand';
238
780
 
239
781
  interface CounterStore {
@@ -293,7 +835,7 @@ export class CounterService {
293
835
  ```ts
294
836
  // counter.module.ts
295
837
 
296
- import { ProviderModule } from '@adimm/x-injection-reactjs';
838
+ import { ProviderModule } from '@adimm/x-injection';
297
839
 
298
840
  import { CounterService } from './counter.service';
299
841
 
@@ -342,138 +884,324 @@ export default Counter;
342
884
  - **Reusability**: Services with stores can be shared across components via dependency injection
343
885
  - **Type safety**: Full TypeScript support throughout
344
886
 
345
- ### Parent-Child Provider Control
887
+ ### Complex Form with Shared State
346
888
 
347
- Parent components can control child component dependencies using the `inject` prop:
889
+ This example demonstrates a powerful pattern: a parent form component controlling the state of multiple child input components.
348
890
 
349
- ```ts
350
- // Child module and service
351
- const ChildModuleBp = ProviderModule.blueprint({
352
- id: 'ChildModule',
353
- providers: [ChildService],
354
- exports: [ChildService],
355
- });
891
+ ```tsx
892
+ import { Inject, Injectable, InjectionScope } from '@adimm/x-injection';
356
893
 
357
- // Parent module
358
- const ParentModuleBp = ProviderModule.blueprint({
359
- id: 'ParentModule',
360
- providers: [ParentService, ChildService],
361
- exports: [ParentService, ChildService],
362
- });
894
+ // 1. Input service - manages a single input's state
895
+ @Injectable()
896
+ class InputService {
897
+ value = '';
898
+ error = '';
363
899
 
364
- // Parent component controls child's service
365
- const ParentComponent = provideModuleToComponent(ParentModuleBp, () => {
366
- const childService = useInject(ChildService);
900
+ setValue(value: string) {
901
+ this.value = value;
902
+ this.validate();
903
+ }
367
904
 
368
- // Override child's ChildService with parent's instance
369
- return <ChildComponent inject={[{ provide: ChildService, useValue: childService }]} />;
370
- });
371
- ```
905
+ validate() {
906
+ if (!this.value) {
907
+ this.error = 'Required';
908
+ } else if (this.value.length < 3) {
909
+ this.error = 'Too short';
910
+ } else {
911
+ this.error = '';
912
+ }
913
+ return !this.error;
914
+ }
915
+ }
372
916
 
373
- This pattern is useful for:
917
+ // 2. Form service - manages the entire form
918
+ @Injectable()
919
+ class FormService {
920
+ constructor(
921
+ public readonly nameInput: InputService,
922
+ public readonly emailInput: InputService
923
+ ) {
924
+ // Initialize with default values
925
+ this.nameInput.setValue('');
926
+ this.emailInput.setValue('');
927
+ }
374
928
 
375
- - Building composable component hierarchies
376
- - Sharing state between parent and child components
377
- - Creating flexible component APIs
929
+ isValid() {
930
+ return this.nameInput.validate() && this.emailInput.validate();
931
+ }
378
932
 
379
- ## Advanced Usage
933
+ submit() {
934
+ if (this.isValid()) {
935
+ console.log('Submitting:', {
936
+ name: this.nameInput.value,
937
+ email: this.emailInput.value,
938
+ });
939
+ }
940
+ }
941
+ }
380
942
 
381
- ### Module Imports and Exports
943
+ // 3. Input component
944
+ const InputModuleBp = ProviderModule.blueprint({
945
+ id: 'InputModule',
946
+ providers: [InputService],
947
+ exports: [InputService],
948
+ });
382
949
 
383
- Modules can import and re-export other modules:
950
+ const Input = provideModuleToComponent<{ label: string }>(InputModuleBp, ({ label }) => {
951
+ const inputService = useInject(InputService);
952
+ const [value, setValue] = useState(inputService.value);
384
953
 
385
- ```ts
386
- const DropdownModuleBp = ProviderModule.blueprint({
387
- id: 'DropdownModule',
388
- imports: [ListviewModuleBp], // Import ListviewModule
389
- providers: [DropdownService],
390
- exports: [
391
- ListviewModuleBp, // Re-export imported module
392
- DropdownService,
954
+ return (
955
+ <div>
956
+ <label>{label}</label>
957
+ <input
958
+ value={value}
959
+ onChange={(e) => {
960
+ setValue(e.target.value);
961
+ inputService.setValue(e.target.value);
962
+ }}
963
+ />
964
+ {inputService.error && <span style={{ color: 'red' }}>{inputService.error}</span>}
965
+ </div>
966
+ );
967
+ });
968
+
969
+ // 4. Form component - injects its InputService instances into child Input components
970
+ const FormModuleBp = ProviderModule.blueprint({
971
+ id: 'FormModule',
972
+ imports: [
973
+ // Clone InputModuleBp and override its defaultScope to Transient for this specific use.
974
+ // Without Transient, both `nameInput` and `emailInput` in FormService would resolve to
975
+ // the same singleton — they'd share state. Transient ensures each @Inject(InputService)
976
+ // parameter in FormService's constructor gets its own independent instance.
977
+ // This is also a good showcase of blueprint dynamicity: the original InputModuleBp is
978
+ // left untouched, and only this consumer opts into Transient behavior.
979
+ InputModuleBp.clone().updateDefinition({
980
+ ...InputModuleBp.getDefinition(),
981
+ defaultScope: InjectionScope.Transient,
982
+ }),
393
983
  ],
984
+ providers: [FormService],
985
+ exports: [FormService],
986
+ });
987
+
988
+ const Form = provideModuleToComponent(FormModuleBp, () => {
989
+ const formService = useInject(FormService);
990
+
991
+ return (
992
+ <form>
993
+ {/* Pass the form's InputService instances to the inputs */}
994
+ <Input inject={[{ provide: InputService, useValue: formService.nameInput }]} label="Name" />
995
+ <Input inject={[{ provide: InputService, useValue: formService.emailInput }]} label="Email" />
996
+ <button type="button" onClick={() => formService.submit()}>
997
+ Submit
998
+ </button>
999
+ </form>
1000
+ );
394
1001
  });
395
1002
  ```
396
1003
 
397
- ### Multiple Dependency Injection
1004
+ **What's happening here?**
398
1005
 
399
- Use `useInjectMany` to inject multiple dependencies:
1006
+ 1. Each `Input` component normally gets its own `InputService`
1007
+ 2. The `Form` component creates two `InputService` instances in its constructor
1008
+ 3. The form **overrides** the input's services using the `inject` prop
1009
+ 4. All inputs share state through the parent form's services
400
1010
 
401
- ```ts
402
- const [userService, apiService] = useInjectMany([UserService, ApiService]);
403
- ```
1011
+ ## Testing Your Code
404
1012
 
405
- ## Unit Testing
1013
+ xInjection makes testing easy. You can mock entire modules or individual services.
406
1014
 
407
- Mock modules easily for testing:
1015
+ ### Mocking an Entire Module
408
1016
 
409
1017
  ```tsx
410
1018
  import { act, render } from '@testing-library/react';
411
1019
 
412
1020
  // Original module
413
- const ApiModuleBp = ProviderModule.blueprint({
414
- id: 'ApiModule',
1021
+ const UserModuleBp = ProviderModule.blueprint({
1022
+ id: 'UserModule',
415
1023
  providers: [UserService, ApiService],
416
1024
  });
417
1025
 
418
- // Create mocked version
419
- const ApiModuleBpMocked = ApiModuleBp.clone().updateDefinition({
420
- id: 'ApiModuleMocked',
1026
+ // Create a mocked version
1027
+ const UserModuleMocked = UserModuleBp.clone().updateDefinition({
1028
+ id: 'UserModuleMocked',
421
1029
  providers: [
422
- { provide: UserService, useClass: UserServiceMock },
1030
+ {
1031
+ provide: UserService,
1032
+ useClass: UserServiceMock, // Your mock class
1033
+ },
423
1034
  {
424
1035
  provide: ApiService,
425
1036
  useValue: {
426
- sendRequest: vi.fn().mockResolvedValue({ data: 'mocked' }),
1037
+ get: vi.fn().mockResolvedValue({ name: 'Test User' }),
1038
+ post: vi.fn(),
427
1039
  },
428
1040
  },
429
1041
  ],
430
1042
  });
431
1043
 
432
- // Test with mocked module
433
- await act(async () => render(<MyComponent module={ApiModuleBpMocked} />));
1044
+ // Test with the mocked module
1045
+ it('should render user data', async () => {
1046
+ await act(async () => render(<UserProfile module={UserModuleMocked} />));
1047
+
1048
+ // Assert...
1049
+ });
434
1050
  ```
435
1051
 
436
- **Testing with Zustand:**
1052
+ ### Mocking on-the-fly
437
1053
 
438
1054
  ```tsx
439
- import { act, renderHook } from '@testing-library/react';
1055
+ import { act, render } from '@testing-library/react';
440
1056
 
441
- import { CounterService } from './counter.service';
1057
+ it('should render user data', async () => {
1058
+ await act(async () =>
1059
+ render(
1060
+ <UserProfile
1061
+ inject={{
1062
+ provide: ApiService,
1063
+ useValue: {
1064
+ get: vi.fn().mockResolvedValue({ name: 'Test User' }),
1065
+ post: vi.fn(),
1066
+ },
1067
+ }}
1068
+ />
1069
+ )
1070
+ );
442
1071
 
443
- it('should increment counter via service', () => {
444
- const service = new CounterService();
1072
+ // Assert...
1073
+ });
1074
+ ```
445
1075
 
446
- const { result } = renderHook(() => service.useStore((s) => s.count));
1076
+ ## FAQ
447
1077
 
448
- expect(result.current).toBe(0);
1078
+ ### How do I add global services?
449
1079
 
450
- act(() => {
451
- service.increment();
452
- });
1080
+ **Recommended:** Use a global blueprint with `isGlobal: true` in your entry point — see [Quick Start](#quick-start) and [Modules: Organizing Dependencies](#2-modules-organizing-dependencies) for the full pattern.
453
1081
 
454
- expect(result.current).toBe(1);
455
- });
1082
+ **For runtime additions**, use the built-in `AppModule` directly:
456
1083
 
457
- it('should handle complex business logic', () => {
458
- const service = new CounterService();
1084
+ ```tsx
1085
+ import { AppModule } from '@adimm/x-injection';
459
1086
 
460
- act(() => {
461
- service.incrementBy(10);
462
- });
1087
+ AppModule.update.addProvider(ApiService, true); // true = also export
1088
+ ```
463
1089
 
464
- expect(service.useStore.getState().count).toBe(10);
465
- });
1090
+ > [!WARNING]
1091
+ > The library provides a built-in `AppModule`. Don't create your own module named "AppModule"—use one of the methods above instead.
1092
+
1093
+ ### When should I use global modules vs component-scoped modules?
1094
+
1095
+ **Global** (`isGlobal: true` + `exports`): API clients, auth state, routing, theme, toast notifications — accessed directly via `useInject` without a HoC.
1096
+
1097
+ **Component-scoped** (blueprint without `isGlobal`): Form state, component-specific business logic, UI state — must use `provideModuleToComponent`; each instance gets its own module.
1098
+
1099
+ ### Can I use this with Redux/MobX/Zustand?
1100
+
1101
+ Yes! xInjection is state-library agnostic. Encapsulate your state management library inside a service:
1102
+
1103
+ ```tsx
1104
+ @Injectable()
1105
+ class TodoStore {
1106
+ private store = create<TodoState>(...);
1107
+
1108
+ get useStore() {
1109
+ return this.store;
1110
+ }
1111
+
1112
+ addTodo(text: string) {
1113
+ this.store.setState(...);
1114
+ }
1115
+ }
466
1116
  ```
467
1117
 
468
- ## Documentation
1118
+ ### How does this compare to React Context?
1119
+
1120
+ | Feature | xInjection | React Context |
1121
+ | ------------------------------- | ---------- | ------------- |
1122
+ | Automatic dependency resolution | ✅ | ❌ |
1123
+ | Component-scoped instances | ✅ | ❌ |
1124
+ | No provider hell | ✅ | ❌ |
1125
+ | Parent-child dependency control | ✅ | ❌ |
1126
+ | Works with class-based logic | ✅ | ❌ |
1127
+ | Testability | ✅ | ⚠️ |
1128
+ | TypeScript support | ✅ | ⚠️ |
1129
+
1130
+ ### If I want Angular patterns, why not just use Angular?
1131
+
1132
+ Because you want React's component model, hooks, and ecosystem — but need better architecture for complex business logic. xInjection brings IoC/DI to React without the framework lock-in.
1133
+
1134
+ That said, if your app is simple, React Context + hooks is perfectly fine. xInjection shines in larger codebases with complex business logic, many modules, or a need for component-scoped service instances.
1135
+
1136
+ ### Can I migrate gradually from an existing React app?
1137
+
1138
+ Absolutely! Start with one component:
1139
+
1140
+ 1. Extract business logic into a service
1141
+ 2. Create a module for that service
1142
+ 3. Wrap the component with `provideModuleToComponent`
1143
+
1144
+ You can use xInjection alongside Context, Redux, or any other state management.
1145
+
1146
+ ### When do I actually need `provideModuleToComponent`?
1147
+
1148
+ **Don't need it (just use `useInject`):** All your services are global/singleton — API client, auth service, theme service.
1149
+
1150
+ **Need it:** You want multiple independent component instances (forms, modals, dialogs), or parent needs to control child dependencies via the `inject` prop.
1151
+
1152
+ See [Why Use the HoC Approach?](#why-use-the-hoc-approach) for a full explanation.
1153
+
1154
+ ### What's the performance impact?
1155
+
1156
+ Minimal. The dependency container is lightweight, and services are created lazily (only when first requested). The HoC pattern has no performance overhead compared to standard React patterns.
1157
+
1158
+ **Runtime vs Build-time:** This library works entirely at runtime (not build-time):
1159
+
1160
+ - Runtime DI is more flexible (dynamic module loading, testing)
1161
+ - Performance impact is negligible (container operations are fast)
1162
+ - You get runtime debugging and introspection
1163
+ - Works with all bundlers/tools without special configuration
1164
+
1165
+ ### Why use classes for services instead of custom hooks?
1166
+
1167
+ Both approaches work! Here's when classes shine:
1168
+
1169
+ **Classes are better for:**
1170
+
1171
+ - Complex business logic (multiple methods, private state)
1172
+ - Dependency injection (automatic wiring)
1173
+ - Testing (easier to mock)
1174
+ - Encapsulation (private members, getters/setters)
1175
+
1176
+ **Hooks are better for:**
1177
+
1178
+ - Simple component logic
1179
+ - React-specific features (useState, useEffect)
1180
+ - Functional programming style
1181
+
1182
+ **You can use both!** Use classes for services, hooks for UI logic. The `hookFactory` even lets you create hooks that inject class-based services.
1183
+
1184
+ **Note:** Services are classes, but components are still functional! You write normal React functional components with hooks—only the business logic is in classes.
1185
+
1186
+ ## Links
469
1187
 
470
1188
  📚 **Full API Documentation:** [https://adimarianmutu.github.io/x-injection-reactjs](https://adimarianmutu.github.io/x-injection-reactjs/index.html)
471
1189
 
472
- For more information about the base library, see [xInjection Documentation](https://github.com/AdiMarianMutu/x-injection#readme).
1190
+ 🔧 **Base Library:** [xInjection](https://github.com/AdiMarianMutu/x-injection)
1191
+
1192
+ 💡 **Issues & Feature Requests:** [GitHub Issues](https://github.com/AdiMarianMutu/x-injection-reactjs/issues)
473
1193
 
474
1194
  ## Contributing
475
1195
 
476
- Pull requests are welcome! Please ensure your contributions follow the project's code style.
1196
+ Contributions are welcome! Please:
1197
+
1198
+ 1. Fork the repository
1199
+ 2. Create a feature branch
1200
+ 3. Make your changes
1201
+ 4. Add tests
1202
+ 5. Submit a pull request
1203
+
1204
+ Please ensure your code follows the project's style and all tests pass.
477
1205
 
478
1206
  ## License
479
1207
 
@@ -481,4 +1209,4 @@ MIT © [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/)
481
1209
 
482
1210
  ---
483
1211
 
484
- **Questions or issues?** Open an [issue on GitHub](https://github.com/AdiMarianMutu/x-injection-reactjs/issues)
1212
+ Made with ❤️ for the React community. If you find this library helpful, consider giving it a ⭐ on [GitHub](https://github.com/AdiMarianMutu/x-injection-reactjs)!
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/AdiMarianMutu/x-injection-reactjs"
6
6
  },
7
7
  "description": "ReactJS integration of the `xInjection` library.",
8
- "version": "1.0.4",
8
+ "version": "1.0.6",
9
9
  "author": "Adi-Marian Mutu",
10
10
  "homepage": "https://github.com/AdiMarianMutu/x-injection-reactjs#readme",
11
11
  "bugs": "https://github.com/AdiMarianMutu/x-injection-reactjs/issues",
@@ -42,7 +42,7 @@
42
42
  "v:bump-major": "npm version major -m \"chore: update lib major version %s\""
43
43
  },
44
44
  "dependencies": {
45
- "@adimm/x-injection": "^3.0.0",
45
+ "@adimm/x-injection": "^3.0.4",
46
46
  "react": ">=18.0.0"
47
47
  },
48
48
  "devDependencies": {