@adimm/x-injection-reactjs 1.0.2 → 1.0.4
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.
- package/README.md +291 -408
- package/dist/index.cjs +44 -43
- package/dist/index.js +55 -54
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -12,61 +12,53 @@
|
|
|
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)_
|
|
16
|
+
|
|
15
17
|
## Table of Contents
|
|
16
18
|
|
|
17
19
|
- [Table of Contents](#table-of-contents)
|
|
18
20
|
- [Overview](#overview)
|
|
19
21
|
- [Installation](#installation)
|
|
20
|
-
|
|
21
|
-
- [
|
|
22
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
|
|
25
|
-
- [Component
|
|
26
|
-
- [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [No](#no)
|
|
30
|
-
- [How to control a Child component providers from Parent component?](#how-to-control-a-child-component-providers-from-parent-component)
|
|
31
|
-
- [Override the entire Child Module](#override-the-entire-child-module)
|
|
32
|
-
- [Override only specific Child Providers](#override-only-specific-child-providers)
|
|
33
|
-
- [Hook Injection](#hook-injection)
|
|
22
|
+
- [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)
|
|
34
31
|
- [Examples](#examples)
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
32
|
+
- [Zustand Integration](#zustand-integration)
|
|
33
|
+
- [Parent-Child Provider Control](#parent-child-provider-control)
|
|
34
|
+
- [Advanced Usage](#advanced-usage)
|
|
35
|
+
- [Module Imports and Exports](#module-imports-and-exports)
|
|
36
|
+
- [Multiple Dependency Injection](#multiple-dependency-injection)
|
|
37
|
+
- [Unit Testing](#unit-testing)
|
|
37
38
|
- [Documentation](#documentation)
|
|
38
39
|
- [Contributing](#contributing)
|
|
39
|
-
- [
|
|
40
|
+
- [License](#license)
|
|
40
41
|
|
|
41
42
|
## Overview
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
> [!Warning]
|
|
46
|
-
>
|
|
47
|
-
> The usage of the `base` library will not be explained here, I'll assume you already know how to use the `xInjection` library, if that's not the case, please refer to the `xInjection` [Gettng Started](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#getting-started) section.
|
|
44
|
+
xInjection for React brings dependency injection to your React components, enabling:
|
|
48
45
|
|
|
49
|
-
|
|
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
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
This is the official [ReactJS](https://react.dev/) implementation of [xInjection](https://github.com/AdiMarianMutu/x-injection).
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
npm i reflect-metadata
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Then install `xInjection` for React:
|
|
53
|
+
## Installation
|
|
58
54
|
|
|
59
55
|
```sh
|
|
60
|
-
npm i @adimm/x-injection-reactjs
|
|
56
|
+
npm i @adimm/x-injection-reactjs reflect-metadata
|
|
61
57
|
```
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
>
|
|
65
|
-
> You may also have to install the parent library via `npm i @adimm/x-injection`
|
|
59
|
+
**TypeScript Configuration**
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
Add the following options to your `tsconfig.json` to enable decorator metadata:
|
|
61
|
+
Add to your `tsconfig.json`:
|
|
70
62
|
|
|
71
63
|
```json
|
|
72
64
|
{
|
|
@@ -77,17 +69,25 @@ Add the following options to your `tsconfig.json` to enable decorator metadata:
|
|
|
77
69
|
}
|
|
78
70
|
```
|
|
79
71
|
|
|
80
|
-
##
|
|
81
|
-
|
|
82
|
-
### Quick Start
|
|
72
|
+
## Quick Start
|
|
83
73
|
|
|
84
74
|
```tsx
|
|
75
|
+
import { Injectable, provideModuleToComponent, ProviderModule, useInject } from '@adimm/x-injection-reactjs';
|
|
76
|
+
|
|
77
|
+
// 1. Define a service
|
|
78
|
+
@Injectable()
|
|
79
|
+
class UserService {
|
|
80
|
+
firstName = 'John';
|
|
81
|
+
lastName = 'Doe';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 2. Create a module blueprint
|
|
85
85
|
const UserDashboardModuleBp = ProviderModule.blueprint({
|
|
86
|
-
id: '
|
|
87
|
-
|
|
88
|
-
exports: [UserModule],
|
|
86
|
+
id: 'UserDashboardModule',
|
|
87
|
+
providers: [UserService],
|
|
89
88
|
});
|
|
90
89
|
|
|
90
|
+
// 3. Create a component with dependency injection
|
|
91
91
|
const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
|
|
92
92
|
const userService = useInject(UserService);
|
|
93
93
|
|
|
@@ -97,505 +97,388 @@ const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
|
|
|
97
97
|
</h1>
|
|
98
98
|
);
|
|
99
99
|
});
|
|
100
|
-
|
|
101
|
-
const App = () => {
|
|
102
|
-
return (
|
|
103
|
-
<>
|
|
104
|
-
<Navbar />
|
|
105
|
-
<UserDashboard />
|
|
106
|
-
<Footer />
|
|
107
|
-
</>
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
100
|
```
|
|
111
101
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
Before continuing you should read also the [Conventions](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#conventions) section of the _parent_ library.
|
|
115
|
-
|
|
116
|
-
### Component Module
|
|
117
|
-
|
|
118
|
-
You should create a separate file which you'll use to declare the `blueprint` of the _component_ `module`:
|
|
119
|
-
|
|
120
|
-
`user-dashboard/user-dashboard.module.ts`
|
|
121
|
-
|
|
122
|
-
```ts
|
|
123
|
-
export const UserDashboardModuleBp = ProviderModule.blueprint({
|
|
124
|
-
id: 'ComponentUserDashboardModule',
|
|
125
|
-
...
|
|
126
|
-
});
|
|
127
|
-
```
|
|
102
|
+
## The Problem
|
|
128
103
|
|
|
129
|
-
|
|
130
|
-
>
|
|
131
|
-
> You should also prefix the `id` of the `blueprints` with `Component` as this will help you to debug your app much more easier when something goes wrong.
|
|
104
|
+
React apps often suffer from **provider hell**, **prop drilling**, and **manual dependency wiring**:
|
|
132
105
|
|
|
133
|
-
###
|
|
106
|
+
### Without xInjection
|
|
134
107
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
108
|
+
```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>;
|
|
144
128
|
}
|
|
145
129
|
```
|
|
146
130
|
|
|
147
|
-
###
|
|
148
|
-
|
|
149
|
-
You first have to either create a `module` or `blueprint`, most of the times you'll use the `blueprint` option, if you are asking yourself how you should decide:
|
|
150
|
-
|
|
151
|
-
#### Is your component re-usable?
|
|
152
|
-
|
|
153
|
-
> Will you have **more** than **one** instance of that component?
|
|
154
|
-
|
|
155
|
-
##### Yes
|
|
156
|
-
|
|
157
|
-
- Then you have to use a `blueprint`, the reason can be understood by reading [this](https://github.com/AdiMarianMutu/x-injection?tab=readme-ov-file#import-behavior).
|
|
158
|
-
|
|
159
|
-
##### No
|
|
160
|
-
|
|
161
|
-
- Then you have to use a raw `module`, the reason is the opposite of the `blueprint` motive.
|
|
162
|
-
|
|
163
|
-
> [!Tip] If the above explaination is clear, please skip to the next section, otherwise keep reading.
|
|
164
|
-
|
|
165
|
-
Imagine that we have a `Button` component, clearly we'll have more than one instance of that component, this means that **each** _instance_ of the `Button` component must have its own `module`, where all the `singletons` will act as singletons _only inside_ the component **instance**.
|
|
166
|
-
|
|
167
|
-
> Therefore we leverage the `blueprint` _import_ behavior to achieve that naturally without additional overhead.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
After you created the `component module`, you can provide it to the actual component by using the [provideModuleToComponent](https://adimarianmutu.github.io/x-injection-reactjs/functions/provideModuleToComponent.html) _([HoC](https://legacy.reactjs.org/docs/higher-order-components.html))_ `method`.
|
|
131
|
+
### With xInjection
|
|
172
132
|
|
|
173
133
|
```tsx
|
|
174
|
-
|
|
175
|
-
|
|
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],
|
|
176
139
|
});
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### How to control a Child component providers from Parent component?
|
|
180
|
-
|
|
181
|
-
If you need this design pattern, it is very easy to implement with `xInjection`, you actually have 2 options:
|
|
182
140
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
```ts
|
|
188
|
-
const ChildModuleBp = ProviderModule.blueprint({
|
|
189
|
-
id: 'ComponentChildModule',
|
|
190
|
-
providers: [ChildService],
|
|
191
|
-
exports: [ChildService],
|
|
192
|
-
});
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
What you can now do is to provide the `ChildService` from the `Parent` component, like this:
|
|
196
|
-
|
|
197
|
-
```ts
|
|
198
|
-
const ParentModuleBp = ProviderModule.blueprint({
|
|
199
|
-
id: 'ComponentParentModule',
|
|
200
|
-
providers: [ParentService, ChildService],
|
|
201
|
-
exports: [ParentService, ChildService],
|
|
141
|
+
// 2. Define component-specific services - Per component
|
|
142
|
+
const UserProfileModuleBp = ProviderModule.blueprint({
|
|
143
|
+
id: 'UserProfileModule',
|
|
144
|
+
providers: [UserProfileService], // Automatically gets ApiService, AuthService, ToastService
|
|
202
145
|
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Then, when you are rendering the `Child` component from **within** the `Parent` component:
|
|
206
|
-
|
|
207
|
-
```ts
|
|
208
|
-
const ParentComponent = provideModuleToComponent(ParentModuleBp, ({ module }) => {
|
|
209
|
-
// the `module` prop is always available and automatically injected into the `props` object.
|
|
210
146
|
|
|
211
|
-
|
|
147
|
+
const UserProfile = provideModuleToComponent(UserProfileModuleBp, () => {
|
|
148
|
+
const userProfile = useInject(UserProfileService);
|
|
149
|
+
// IoC automatically injects: ToastService → ApiService → AuthService → UserProfileService
|
|
150
|
+
return <div>{userProfile.displayName}</div>;
|
|
212
151
|
});
|
|
213
152
|
```
|
|
214
153
|
|
|
215
|
-
|
|
154
|
+
**What You Get:**
|
|
216
155
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
|
220
162
|
|
|
221
|
-
|
|
163
|
+
## Core Concepts
|
|
222
164
|
|
|
223
|
-
|
|
165
|
+
### Component Modules
|
|
224
166
|
|
|
225
|
-
|
|
167
|
+
Create a module blueprint that defines your component's dependencies:
|
|
226
168
|
|
|
227
169
|
```ts
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
170
|
+
// user-dashboard.module.ts
|
|
171
|
+
export const UserDashboardModuleBp = ProviderModule.blueprint({
|
|
172
|
+
id: 'UserDashboardModule',
|
|
173
|
+
providers: [UserService],
|
|
174
|
+
exports: [UserService],
|
|
232
175
|
});
|
|
233
176
|
```
|
|
234
177
|
|
|
235
|
-
|
|
178
|
+
**Blueprint vs Module:** Use a **blueprint** for reusable components (multiple instances), use a raw **module** for singleton components (single instance).
|
|
179
|
+
|
|
180
|
+
### Services
|
|
181
|
+
|
|
182
|
+
Define services using the `@Injectable()` decorator:
|
|
236
183
|
|
|
237
184
|
```ts
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
185
|
+
// user-dashboard.service.ts
|
|
186
|
+
@Injectable()
|
|
187
|
+
export class UserDashboardService {
|
|
188
|
+
firstName: string;
|
|
189
|
+
lastName: string;
|
|
241
190
|
|
|
242
|
-
|
|
243
|
-
}
|
|
191
|
+
getFullName() {
|
|
192
|
+
return `${this.firstName} ${this.lastName}`;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
244
195
|
```
|
|
245
196
|
|
|
246
|
-
|
|
197
|
+
### Dependency Injection
|
|
247
198
|
|
|
248
|
-
|
|
249
|
-
>
|
|
250
|
-
> If you are asking yourself `Why would I want to do that?`, that's a valid question, and most of the times you'll **not** need this feature, but sometimes, when you _compose_ components, being able to control the _providers_ of the children components becomes very useful. Check the [Composable Components](#composable-components) example to understand.
|
|
199
|
+
Use `useInject` to access services in your components:
|
|
251
200
|
|
|
252
|
-
|
|
201
|
+
```tsx
|
|
202
|
+
const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
|
|
203
|
+
const userService = useInject(UserService);
|
|
253
204
|
|
|
254
|
-
|
|
205
|
+
return <div>{userService.getFullName()}</div>;
|
|
206
|
+
});
|
|
207
|
+
```
|
|
255
208
|
|
|
256
|
-
|
|
209
|
+
### Custom Hooks
|
|
257
210
|
|
|
258
|
-
|
|
211
|
+
Create reusable hooks with dependency injection using `hookFactory`:
|
|
259
212
|
|
|
260
213
|
```ts
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// The 1st generic param must be the hook params (Like `UserInfoProps`)
|
|
264
|
-
// and the 2nd generic param must be an `array` with the providers type.
|
|
265
|
-
const useGenerateUserFullName = hookFactory({
|
|
266
|
-
// The `use` property is where you write your hook implementation.
|
|
267
|
-
use: ({ firstName, lastName, deps: [userService] }: HookWithDeps<UserInfoProps, [UserService]>) => {
|
|
214
|
+
const useUserFullName = hookFactory({
|
|
215
|
+
use: ({ firstName, lastName, deps: [userService] }) => {
|
|
268
216
|
userService.firstName = firstName;
|
|
269
217
|
userService.lastName = lastName;
|
|
270
|
-
|
|
271
|
-
return userService.generateFullName();
|
|
218
|
+
return userService.getFullName();
|
|
272
219
|
},
|
|
273
|
-
// The `inject` array is very important,
|
|
274
|
-
// here we basically specify which dependencies should be injected into the custom hook.
|
|
275
|
-
// Also, keep in mind that the order of the `inject` array matters, the order of the `deps` prop
|
|
276
|
-
// is determined by the order of the `inject` array!
|
|
277
220
|
inject: [UserService],
|
|
278
221
|
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Now you can use it in inside any component which is using a `module` which can provide the `UserService`.
|
|
282
222
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const userFullName = useGenerateFullName({ firstName, lastName });
|
|
286
|
-
|
|
287
|
-
return <p>Hello {userFullName}!</p>;
|
|
288
|
-
}
|
|
223
|
+
// Use in any component
|
|
224
|
+
const fullName = useUserFullName({ firstName: 'John', lastName: 'Doe' });
|
|
289
225
|
```
|
|
290
226
|
|
|
291
|
-
> **Note:** _If your custom hook does not accept any parameter, you can provide `void` to the 1st generic type._
|
|
292
|
-
>
|
|
293
|
-
> e.g: `use: ({ deps: [userService] }: HookWithDeps<void, [UserService]>)`
|
|
294
|
-
|
|
295
227
|
## Examples
|
|
296
228
|
|
|
297
|
-
###
|
|
298
|
-
|
|
299
|
-
In a real world scenario, you'll definitely have custom components which render other custom components and so on... _(like a [Matryoshka doll](https://en.wikipedia.org/wiki/Matryoshka_doll))_
|
|
300
|
-
|
|
301
|
-
So you may find yourself wanting to be able to control a dependency/service of a child component from a parent component, with `xInjection` this is very easy to achieve thanks to the `ProviderModule` architecture, because each `module` can `import` and `export` other dependencies _(or modules)_ it fits in perfectly within the [declarative programming](https://en.wikipedia.org/wiki/Declarative_programming) world!
|
|
229
|
+
### Zustand Integration
|
|
302
230
|
|
|
303
|
-
|
|
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.
|
|
304
232
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
- A `dropdown`
|
|
308
|
-
- An `autocomplete`
|
|
309
|
-
|
|
310
|
-
<hr>
|
|
233
|
+
```ts
|
|
234
|
+
// counter.service.ts
|
|
311
235
|
|
|
312
|
-
|
|
236
|
+
import { Injectable } from '@adimm/x-injection-reactjs';
|
|
237
|
+
import { create } from 'zustand';
|
|
313
238
|
|
|
314
|
-
|
|
239
|
+
interface CounterStore {
|
|
240
|
+
count: number;
|
|
241
|
+
increment: () => void;
|
|
242
|
+
decrement: () => void;
|
|
243
|
+
reset: () => void;
|
|
244
|
+
}
|
|
315
245
|
|
|
316
|
-
```ts
|
|
317
246
|
@Injectable()
|
|
318
|
-
export class
|
|
319
|
-
|
|
247
|
+
export class CounterService {
|
|
248
|
+
// Store instance encapsulated within the service
|
|
249
|
+
private readonly store = create<CounterStore>((set) => ({
|
|
250
|
+
count: 0,
|
|
251
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
252
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
253
|
+
reset: () => set({ count: 0 }),
|
|
254
|
+
}));
|
|
255
|
+
|
|
256
|
+
// Expose store hook for components to subscribe
|
|
257
|
+
get useStore() {
|
|
258
|
+
return this.store;
|
|
259
|
+
}
|
|
320
260
|
|
|
321
|
-
//
|
|
322
|
-
|
|
261
|
+
// Getter to access current state from within the service
|
|
262
|
+
private get storeState() {
|
|
263
|
+
return this.store.getState();
|
|
264
|
+
}
|
|
323
265
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.
|
|
266
|
+
// Business logic methods
|
|
267
|
+
increment() {
|
|
268
|
+
this.storeState.increment();
|
|
269
|
+
}
|
|
327
270
|
|
|
328
|
-
|
|
271
|
+
decrement() {
|
|
272
|
+
this.storeState.decrement();
|
|
329
273
|
}
|
|
330
|
-
}
|
|
331
274
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
exports: [InputboxService],
|
|
336
|
-
});
|
|
337
|
-
```
|
|
275
|
+
reset() {
|
|
276
|
+
this.storeState.reset();
|
|
277
|
+
}
|
|
338
278
|
|
|
339
|
-
|
|
279
|
+
incrementBy(amount: number) {
|
|
280
|
+
// Complex logic lives in the service
|
|
281
|
+
const currentCount = this.storeState.count;
|
|
282
|
+
this.store.setState({ count: currentCount + amount });
|
|
283
|
+
}
|
|
340
284
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
285
|
+
async incrementAsync() {
|
|
286
|
+
// Handle async operations in the service
|
|
287
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
288
|
+
this.increment();
|
|
289
|
+
}
|
|
344
290
|
}
|
|
345
|
-
|
|
346
|
-
export const Inputbox = provideModuleToComponent<InputboxProps>(InputboxModuleBp, ({ initialValue }) => {
|
|
347
|
-
const service = useInject(InputboxService);
|
|
348
|
-
const [, setCurrentValue] = useState(initialValue);
|
|
349
|
-
service.setStateValue = setCurrentValue;
|
|
350
|
-
|
|
351
|
-
useEffect(() => {
|
|
352
|
-
service.currentValue = initialValue;
|
|
353
|
-
}, [initialValue]);
|
|
354
|
-
|
|
355
|
-
return <input value={service.currentValue} onChange={(e) => service.setValue(e.currentTarget.value)} />;
|
|
356
|
-
});
|
|
357
291
|
```
|
|
358
292
|
|
|
359
|
-
<hr>
|
|
360
|
-
|
|
361
|
-
> Listview
|
|
362
|
-
|
|
363
|
-
`listview.service.ts`
|
|
364
|
-
|
|
365
293
|
```ts
|
|
366
|
-
|
|
367
|
-
export class ListviewService {
|
|
368
|
-
items = [];
|
|
294
|
+
// counter.module.ts
|
|
369
295
|
|
|
370
|
-
|
|
371
|
-
|
|
296
|
+
import { ProviderModule } from '@adimm/x-injection-reactjs';
|
|
297
|
+
|
|
298
|
+
import { CounterService } from './counter.service';
|
|
372
299
|
|
|
373
|
-
export const
|
|
374
|
-
id: '
|
|
375
|
-
|
|
376
|
-
exports: [
|
|
300
|
+
export const CounterModuleBp = ProviderModule.blueprint({
|
|
301
|
+
id: 'CounterModule',
|
|
302
|
+
providers: [CounterService],
|
|
303
|
+
exports: [CounterService],
|
|
377
304
|
});
|
|
378
305
|
```
|
|
379
306
|
|
|
380
|
-
`listview.tsx`
|
|
381
|
-
|
|
382
307
|
```tsx
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
}
|
|
308
|
+
// counter.component.tsx
|
|
309
|
+
|
|
310
|
+
import { provideModuleToComponent, useInject } from '@adimm/x-injection-reactjs';
|
|
311
|
+
|
|
312
|
+
import { CounterModuleBp } from './counter.module';
|
|
313
|
+
import { CounterService } from './counter.service';
|
|
386
314
|
|
|
387
|
-
|
|
388
|
-
|
|
315
|
+
const Counter = provideModuleToComponent(CounterModuleBp, () => {
|
|
316
|
+
// Inject service for business logic
|
|
317
|
+
const counterService = useInject(CounterService);
|
|
389
318
|
|
|
390
|
-
|
|
319
|
+
// Subscribe to store for reactive state
|
|
320
|
+
const count = counterService.useStore((state) => state.count);
|
|
391
321
|
|
|
392
322
|
return (
|
|
393
323
|
<div>
|
|
394
|
-
{
|
|
395
|
-
|
|
396
|
-
))}
|
|
324
|
+
<h2>Count: {count}</h2>
|
|
325
|
+
<button onClick={() => counterService.increment()}>+1</button>
|
|
326
|
+
<button onClick={() => counterService.decrement()}>-1</button>
|
|
327
|
+
<button onClick={() => counterService.incrementBy(5)}>+5</button>
|
|
328
|
+
<button onClick={() => counterService.incrementAsync()}>+1 Async</button>
|
|
329
|
+
<button onClick={() => counterService.reset()}>Reset</button>
|
|
397
330
|
</div>
|
|
398
331
|
);
|
|
399
332
|
});
|
|
333
|
+
|
|
334
|
+
export default Counter;
|
|
400
335
|
```
|
|
401
336
|
|
|
402
|
-
|
|
337
|
+
**Key Benefits:**
|
|
403
338
|
|
|
404
|
-
|
|
339
|
+
- **Encapsulation**: Store is encapsulated within the service, not exposed globally
|
|
340
|
+
- **Separation of concerns**: Business logic in services, UI only subscribes to state
|
|
341
|
+
- **Testability**: Services are self-contained and easy to test
|
|
342
|
+
- **Reusability**: Services with stores can be shared across components via dependency injection
|
|
343
|
+
- **Type safety**: Full TypeScript support throughout
|
|
405
344
|
|
|
406
|
-
|
|
345
|
+
### Parent-Child Provider Control
|
|
407
346
|
|
|
408
|
-
`
|
|
347
|
+
Parent components can control child component dependencies using the `inject` prop:
|
|
409
348
|
|
|
410
349
|
```ts
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/* Remaining fancy implementation */
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
export const DropdownModuleBp = ProviderModule.blueprint({
|
|
422
|
-
id: 'ComponentDropdownModule',
|
|
423
|
-
// It is very important that we import all the exportable dependencies from the `ListviewModule`!
|
|
424
|
-
imports: [ListviewModuleBp],
|
|
425
|
-
provides: [DropdownService],
|
|
426
|
-
exports: [
|
|
427
|
-
// Let's also re-export the dependencies of the `ListviewModule` so once we import the `DropdownModule`
|
|
428
|
-
// somewhere elese, we get access to the `ListviewModule` exported dependencies as well!
|
|
429
|
-
ListviewModuleBp,
|
|
430
|
-
// Let's not forget to also export our `DropdownService` :)
|
|
431
|
-
DropdownService,
|
|
432
|
-
],
|
|
350
|
+
// Child module and service
|
|
351
|
+
const ChildModuleBp = ProviderModule.blueprint({
|
|
352
|
+
id: 'ChildModule',
|
|
353
|
+
providers: [ChildService],
|
|
354
|
+
exports: [ChildService],
|
|
433
355
|
});
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
`dropdown.tsx`
|
|
437
|
-
|
|
438
|
-
```tsx
|
|
439
|
-
export interface DropdownProps {
|
|
440
|
-
listviewProps: ListviewProps;
|
|
441
|
-
|
|
442
|
-
initialSelectedValue: number;
|
|
443
|
-
}
|
|
444
356
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
357
|
+
// Parent module
|
|
358
|
+
const ParentModuleBp = ProviderModule.blueprint({
|
|
359
|
+
id: 'ParentModule',
|
|
360
|
+
providers: [ParentService, ChildService],
|
|
361
|
+
exports: [ParentService, ChildService],
|
|
362
|
+
});
|
|
451
363
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
364
|
+
// Parent component controls child's service
|
|
365
|
+
const ParentComponent = provideModuleToComponent(ParentModuleBp, () => {
|
|
366
|
+
const childService = useInject(ChildService);
|
|
455
367
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
);
|
|
368
|
+
// Override child's ChildService with parent's instance
|
|
369
|
+
return <ChildComponent inject={[{ provide: ChildService, useValue: childService }]} />;
|
|
370
|
+
});
|
|
463
371
|
```
|
|
464
372
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
> Autocomplete
|
|
373
|
+
This pattern is useful for:
|
|
468
374
|
|
|
469
|
-
|
|
375
|
+
- Building composable component hierarchies
|
|
376
|
+
- Sharing state between parent and child components
|
|
377
|
+
- Creating flexible component APIs
|
|
470
378
|
|
|
471
|
-
|
|
379
|
+
## Advanced Usage
|
|
472
380
|
|
|
473
|
-
|
|
474
|
-
@Injectable()
|
|
475
|
-
export class AutocompleteService {
|
|
476
|
-
constructor(
|
|
477
|
-
readonly inputboxService: InputboxService,
|
|
478
|
-
readonly dropdownService: DropdownService
|
|
479
|
-
) {
|
|
480
|
-
// Here we can override even what the `Dropdown` has already overriden!
|
|
481
|
-
this.dropdownService.listviewService.items = [29, 9, 1969];
|
|
482
|
-
|
|
483
|
-
// However doing the following, will throw an error because the `Inputbox` component
|
|
484
|
-
// at this time is not yet mounted, therefore the `setStateValue` state setter
|
|
485
|
-
// method doesn't exist yet.
|
|
486
|
-
//
|
|
487
|
-
// A better way would be to use a store manager so you can generate your application state through
|
|
488
|
-
// the services, rather than inside the UI (components should be used only to render the data, not to manipulate/manage it).
|
|
489
|
-
this.inputboxService.setValue('xInjection');
|
|
490
|
-
}
|
|
381
|
+
### Module Imports and Exports
|
|
491
382
|
|
|
492
|
-
|
|
493
|
-
}
|
|
383
|
+
Modules can import and re-export other modules:
|
|
494
384
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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,
|
|
393
|
+
],
|
|
501
394
|
});
|
|
502
395
|
```
|
|
503
396
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
```tsx
|
|
507
|
-
export interface AutocompleteProps {
|
|
508
|
-
inputboxProps: InputboxProps;
|
|
509
|
-
dropdownProps: DropdownProps;
|
|
510
|
-
|
|
511
|
-
currentText: string;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
export const Autocomplete = provideModuleToComponent<AutocompleteProps>(AutocompleteModuleBp, ({ inputboxProps, dropdownProps, currentText }) => {
|
|
515
|
-
const service = useInject(AutocompleteService);
|
|
397
|
+
### Multiple Dependency Injection
|
|
516
398
|
|
|
517
|
-
|
|
399
|
+
Use `useInjectMany` to inject multiple dependencies:
|
|
518
400
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
/* Remaining fancy implementation */
|
|
523
|
-
|
|
524
|
-
return (
|
|
525
|
-
<div className="fancy-autocomplete">
|
|
526
|
-
{/* Let's not forget to replace the injection providers of both components we want to control */}
|
|
527
|
-
<Inputbox {...inputboxProps} inject={[{ provide: InputboxService, useValue: service.inputboxService }]} >
|
|
528
|
-
<Dropdown {...dropdownProps} inject={[{ provide: DropdownService, useValue: service.dropdownService }]} />
|
|
529
|
-
</div>
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
);
|
|
401
|
+
```ts
|
|
402
|
+
const [userService, apiService] = useInjectMany([UserService, ApiService]);
|
|
533
403
|
```
|
|
534
404
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
## Unit Tests
|
|
405
|
+
## Unit Testing
|
|
538
406
|
|
|
539
|
-
|
|
407
|
+
Mock modules easily for testing:
|
|
540
408
|
|
|
541
409
|
```tsx
|
|
542
|
-
|
|
543
|
-
constructor(private readonly userService: UserService) {}
|
|
544
|
-
|
|
545
|
-
async sendRequest<T>(location: LocationParams): Promise<T> {
|
|
546
|
-
// Pseudo Implementation
|
|
547
|
-
return this.sendToLocation(user, location);
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
private async sendToLocation(user: User, location: any): Promise<any> {}
|
|
551
|
-
}
|
|
410
|
+
import { act, render } from '@testing-library/react';
|
|
552
411
|
|
|
553
|
-
|
|
412
|
+
// Original module
|
|
413
|
+
const ApiModuleBp = ProviderModule.blueprint({
|
|
554
414
|
id: 'ApiModule',
|
|
555
415
|
providers: [UserService, ApiService],
|
|
556
416
|
});
|
|
557
417
|
|
|
558
|
-
//
|
|
418
|
+
// Create mocked version
|
|
559
419
|
const ApiModuleBpMocked = ApiModuleBp.clone().updateDefinition({
|
|
560
420
|
id: 'ApiModuleMocked',
|
|
561
421
|
providers: [
|
|
562
|
-
{
|
|
563
|
-
provide: UserService,
|
|
564
|
-
useClass: UserService_Mock,
|
|
565
|
-
},
|
|
422
|
+
{ provide: UserService, useClass: UserServiceMock },
|
|
566
423
|
{
|
|
567
424
|
provide: ApiService,
|
|
568
425
|
useValue: {
|
|
569
|
-
sendRequest:
|
|
570
|
-
console.log(location);
|
|
571
|
-
},
|
|
426
|
+
sendRequest: vi.fn().mockResolvedValue({ data: 'mocked' }),
|
|
572
427
|
},
|
|
573
428
|
},
|
|
574
429
|
],
|
|
575
430
|
});
|
|
576
431
|
|
|
577
|
-
//
|
|
578
|
-
await act(async () => render(<
|
|
432
|
+
// Test with mocked module
|
|
433
|
+
await act(async () => render(<MyComponent module={ApiModuleBpMocked} />));
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Testing with Zustand:**
|
|
437
|
+
|
|
438
|
+
```tsx
|
|
439
|
+
import { act, renderHook } from '@testing-library/react';
|
|
440
|
+
|
|
441
|
+
import { CounterService } from './counter.service';
|
|
442
|
+
|
|
443
|
+
it('should increment counter via service', () => {
|
|
444
|
+
const service = new CounterService();
|
|
445
|
+
|
|
446
|
+
const { result } = renderHook(() => service.useStore((s) => s.count));
|
|
447
|
+
|
|
448
|
+
expect(result.current).toBe(0);
|
|
449
|
+
|
|
450
|
+
act(() => {
|
|
451
|
+
service.increment();
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
expect(result.current).toBe(1);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('should handle complex business logic', () => {
|
|
458
|
+
const service = new CounterService();
|
|
459
|
+
|
|
460
|
+
act(() => {
|
|
461
|
+
service.incrementBy(10);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
expect(service.useStore.getState().count).toBe(10);
|
|
465
|
+
});
|
|
579
466
|
```
|
|
580
467
|
|
|
581
468
|
## Documentation
|
|
582
469
|
|
|
583
|
-
|
|
470
|
+
📚 **Full API Documentation:** [https://adimarianmutu.github.io/x-injection-reactjs](https://adimarianmutu.github.io/x-injection-reactjs/index.html)
|
|
584
471
|
|
|
585
|
-
|
|
472
|
+
For more information about the base library, see [xInjection Documentation](https://github.com/AdiMarianMutu/x-injection#readme).
|
|
586
473
|
|
|
587
474
|
## Contributing
|
|
588
475
|
|
|
589
|
-
Pull requests are
|
|
590
|
-
|
|
591
|
-
Please ensure your contributions adhere to the project's code style. See the repository for more details.
|
|
476
|
+
Pull requests are welcome! Please ensure your contributions follow the project's code style.
|
|
592
477
|
|
|
593
|
-
##
|
|
478
|
+
## License
|
|
594
479
|
|
|
595
|
-
|
|
480
|
+
MIT © [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/)
|
|
596
481
|
|
|
597
482
|
---
|
|
598
483
|
|
|
599
|
-
|
|
600
|
-
>
|
|
601
|
-
> **For questions, feature requests, or bug reports, feel free to open an [issue](https://github.com/AdiMarianMutu/x-injection-react/issues) on GitHub.**
|
|
484
|
+
**Questions or issues?** Open an [issue on GitHub](https://github.com/AdiMarianMutu/x-injection-reactjs/issues)
|
package/dist/index.cjs
CHANGED
|
@@ -9,91 +9,92 @@ var e, r = Object.create, t = Object.defineProperty, o = Object.getOwnPropertyDe
|
|
|
9
9
|
enumerable: !(c = o(r, a)) || c.enumerable
|
|
10
10
|
});
|
|
11
11
|
return e;
|
|
12
|
-
},
|
|
12
|
+
}, s = {};
|
|
13
13
|
|
|
14
14
|
((e, r) => {
|
|
15
15
|
for (var o in r) t(e, o, {
|
|
16
16
|
get: r[o],
|
|
17
17
|
enumerable: !0
|
|
18
18
|
});
|
|
19
|
-
})(
|
|
19
|
+
})(s, {
|
|
20
20
|
REACT_X_INJECTION_PROVIDER_MODULE_CONTEXT: () => l,
|
|
21
|
-
hookFactory: () =>
|
|
21
|
+
hookFactory: () => q,
|
|
22
22
|
provideModuleToComponent: () => T,
|
|
23
|
-
useComponentModule: () =>
|
|
24
|
-
useInject: () =>
|
|
23
|
+
useComponentModule: () => f,
|
|
24
|
+
useInject: () => m,
|
|
25
25
|
useInjectMany: () => v
|
|
26
|
-
}), module.exports = (e =
|
|
26
|
+
}), module.exports = (e = s, a(t({}, "__esModule", {
|
|
27
27
|
value: !0
|
|
28
28
|
}), e));
|
|
29
29
|
|
|
30
|
-
var
|
|
30
|
+
var d = require("@adimm/x-injection"), l = (0, require("react").createContext)(d.AppModule), p = require("react");
|
|
31
31
|
|
|
32
|
-
function
|
|
32
|
+
function f() {
|
|
33
33
|
return (0, p.useContext)(l);
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function
|
|
37
|
-
return
|
|
36
|
+
function m(e, r) {
|
|
37
|
+
return f().get(e, r?.isOptional, r?.asList);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
function v(...e) {
|
|
41
|
-
return
|
|
41
|
+
return f().getMany(...e);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
c(
|
|
44
|
+
c(f, "useComponentModule"), c(m, "useInject"), c(v, "useInjectMany");
|
|
45
45
|
|
|
46
|
-
var M = ((e, o, n) => (n = null != e ? r(u(e)) : {}, a(!o && e && e.__esModule ? n : t(n, "default", {
|
|
46
|
+
var M, y = ((e, o, n) => (n = null != e ? r(u(e)) : {}, a(!o && e && e.__esModule ? n : t(n, "default", {
|
|
47
47
|
value: e,
|
|
48
48
|
enumerable: !0
|
|
49
|
-
}), e)))(require("react"), 1), j = require("@adimm/x-injection"), P = require("react"),
|
|
49
|
+
}), e)))(require("react"), 1), j = require("@adimm/x-injection"), P = require("react"), b = require("react");
|
|
50
50
|
|
|
51
|
-
function
|
|
52
|
-
const r = (0,
|
|
53
|
-
|
|
54
|
-
t.current && (o.current = !0), (0,
|
|
55
|
-
t.current = !0), n(e => e + 1), () => {
|
|
51
|
+
function h(e) {
|
|
52
|
+
const r = (0, b.useRef)(void 0), t = (0, b.useRef)(!1), o = (0, b.useRef)(!1), [, n] = (0,
|
|
53
|
+
b.useState)(0);
|
|
54
|
+
t.current && (o.current = !0), (0, b.useEffect)((() => (t.current || (r.current = e(),
|
|
55
|
+
t.current = !0), n((e => e + 1)), () => {
|
|
56
56
|
o.current && r.current?.();
|
|
57
|
-
}), []);
|
|
57
|
+
})), []);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function O(e, r) {
|
|
61
|
-
const t = (0, P.useMemo)(() => {
|
|
61
|
+
const t = (0, P.useMemo)((() => {
|
|
62
62
|
const {module: t, inject: o} = r ?? {}, n = j.ProviderModuleHelpers.tryBlueprintToModule(t ?? e);
|
|
63
63
|
if (n.options.isGlobal) throw new j.InjectionProviderModuleError(n, "A 'global' module can't be supplied to a component!");
|
|
64
|
-
return o?.forEach(e => {
|
|
64
|
+
return o?.forEach((e => {
|
|
65
65
|
if (!n.hasProvider(e)) throw new j.InjectionProviderModuleError(n, `The [${j.ProviderTokenHelpers.providerTokenToString(e)}] provider can't be replaced because it is not part of the component module!`);
|
|
66
66
|
n.update.removeProvider(j.ProviderTokenHelpers.toProviderIdentifier(e)), n.update.addProvider(e);
|
|
67
|
-
}), n;
|
|
68
|
-
}, [ e, r?.inject ]);
|
|
69
|
-
return
|
|
67
|
+
})), n;
|
|
68
|
+
}), [ e, r?.inject ]);
|
|
69
|
+
return h((() => () => {
|
|
70
70
|
t.dispose();
|
|
71
|
-
}), t;
|
|
71
|
+
})), t;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
function g(e) {
|
|
75
|
+
return "function" == typeof e && !Function.prototype.toString.call(e).startsWith("class ");
|
|
76
|
+
}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
c(h, "useEffectOnce"), c(O, "useMakeOrGetComponentModule"), c(g, "isFunction"),
|
|
79
|
+
function(e) {
|
|
79
80
|
function r(e, r, t) {
|
|
80
81
|
const o = {
|
|
81
82
|
...r
|
|
82
83
|
};
|
|
83
|
-
return ("object" == typeof e && "type" in e && (
|
|
84
|
-
|
|
84
|
+
return ("object" == typeof e && "type" in e && g(e.type) || g(e)) && (o.module = t),
|
|
85
|
+
o;
|
|
85
86
|
}
|
|
86
87
|
c(r, "forwardPropsWithModule"), e.forwardPropsWithModule = r;
|
|
87
|
-
}(
|
|
88
|
+
}(M || (M = {}));
|
|
88
89
|
|
|
89
|
-
var
|
|
90
|
+
var E = y.default.memo(w);
|
|
90
91
|
|
|
91
92
|
function T(e, r) {
|
|
92
93
|
return t => {
|
|
93
94
|
const o = O(e, t);
|
|
94
|
-
return
|
|
95
|
+
return y.default.createElement(l.Provider, {
|
|
95
96
|
value: o
|
|
96
|
-
},
|
|
97
|
+
}, y.default.createElement(E, {
|
|
97
98
|
module: o,
|
|
98
99
|
componentProps: t,
|
|
99
100
|
component: r
|
|
@@ -102,24 +103,24 @@ function T(e, r) {
|
|
|
102
103
|
}
|
|
103
104
|
|
|
104
105
|
function w({module: e, component: r, componentProps: t}) {
|
|
105
|
-
return
|
|
106
|
+
return y.default.createElement(y.default.Fragment, null, r(M.forwardPropsWithModule(r, t, e)));
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
c(T, "provideModuleToComponent"), c(w, "_ComponentRenderer");
|
|
109
110
|
|
|
110
|
-
var I = require("react"), C = require("@adimm/x-injection"),
|
|
111
|
+
var I = require("react"), C = require("@adimm/x-injection"), _ = class e extends C.InjectionProviderModuleError {
|
|
111
112
|
static {
|
|
112
113
|
c(this, "InjectionHookFactoryError");
|
|
113
114
|
}
|
|
114
115
|
name=e.name;
|
|
115
116
|
};
|
|
116
117
|
|
|
117
|
-
function
|
|
118
|
+
function q({use: e, inject: r}) {
|
|
118
119
|
return t => {
|
|
119
|
-
const o =
|
|
120
|
-
if (0 === r.length) throw new
|
|
120
|
+
const o = f(), n = (0, I.useMemo)((() => {
|
|
121
|
+
if (0 === r.length) throw new _(o, "The 'deps' property array is missing!");
|
|
121
122
|
return o.getMany(...r);
|
|
122
|
-
}, [ r ]);
|
|
123
|
+
}), [ r ]);
|
|
123
124
|
return e({
|
|
124
125
|
...t,
|
|
125
126
|
deps: [ ...n ]
|
|
@@ -127,4 +128,4 @@ function _({use: e, inject: r}) {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
c(
|
|
131
|
+
c(q, "hookFactory");
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
var e = Object.defineProperty,
|
|
2
|
-
value:
|
|
1
|
+
var e = Object.defineProperty, o = (o, r) => e(o, "name", {
|
|
2
|
+
value: r,
|
|
3
3
|
configurable: !0
|
|
4
4
|
});
|
|
5
5
|
|
|
6
|
-
import { AppModule as
|
|
6
|
+
import { AppModule as r } from "@adimm/x-injection";
|
|
7
7
|
|
|
8
8
|
import { createContext as t } from "react";
|
|
9
9
|
|
|
10
|
-
var n, i = t(
|
|
10
|
+
var n, i = t(r);
|
|
11
11
|
|
|
12
12
|
import { useContext as c } from "react";
|
|
13
13
|
|
|
@@ -15,106 +15,107 @@ function u() {
|
|
|
15
15
|
return c(i);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
function
|
|
19
|
-
return u().get(e,
|
|
18
|
+
function a(e, o) {
|
|
19
|
+
return u().get(e, o?.isOptional, o?.asList);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
function
|
|
22
|
+
function m(...e) {
|
|
23
23
|
return u().getMany(...e);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
o(u, "useComponentModule"), o(a, "useInject"), o(m, "useInjectMany");
|
|
27
27
|
|
|
28
28
|
import p from "react";
|
|
29
29
|
|
|
30
|
-
import { InjectionProviderModuleError as
|
|
30
|
+
import { InjectionProviderModuleError as s, ProviderModuleHelpers as d, ProviderTokenHelpers as f } from "@adimm/x-injection";
|
|
31
31
|
|
|
32
32
|
import { useMemo as l } from "react";
|
|
33
33
|
|
|
34
|
-
import { useEffect as v, useRef as h, useState as
|
|
34
|
+
import { useEffect as v, useRef as h, useState as y } from "react";
|
|
35
35
|
|
|
36
36
|
function M(e) {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
n(e => e + 1), () => {
|
|
40
|
-
t.current &&
|
|
41
|
-
}), []);
|
|
37
|
+
const o = h(void 0), r = h(!1), t = h(!1), [, n] = y(0);
|
|
38
|
+
r.current && (t.current = !0), v((() => (r.current || (o.current = e(), r.current = !0),
|
|
39
|
+
n((e => e + 1)), () => {
|
|
40
|
+
t.current && o.current?.();
|
|
41
|
+
})), []);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
46
|
-
const {module:
|
|
47
|
-
if (n.options.isGlobal) throw new
|
|
48
|
-
return t?.forEach(e => {
|
|
49
|
-
if (!n.hasProvider(e)) throw new
|
|
44
|
+
function j(e, o) {
|
|
45
|
+
const r = l((() => {
|
|
46
|
+
const {module: r, inject: t} = o ?? {}, n = d.tryBlueprintToModule(r ?? e);
|
|
47
|
+
if (n.options.isGlobal) throw new s(n, "A 'global' module can't be supplied to a component!");
|
|
48
|
+
return t?.forEach((e => {
|
|
49
|
+
if (!n.hasProvider(e)) throw new s(n, `The [${f.providerTokenToString(e)}] provider can't be replaced because it is not part of the component module!`);
|
|
50
50
|
n.update.removeProvider(f.toProviderIdentifier(e)), n.update.addProvider(e);
|
|
51
|
-
}), n;
|
|
52
|
-
}, [ e,
|
|
53
|
-
return M(() => () => {
|
|
54
|
-
|
|
55
|
-
}),
|
|
51
|
+
})), n;
|
|
52
|
+
}), [ e, o?.inject ]);
|
|
53
|
+
return M((() => () => {
|
|
54
|
+
r.dispose();
|
|
55
|
+
})), r;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
function P(e) {
|
|
59
|
+
return "function" == typeof e && !Function.prototype.toString.call(e).startsWith("class ");
|
|
60
|
+
}
|
|
61
61
|
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
o(M, "useEffectOnce"), o(j, "useMakeOrGetComponentModule"), o(P, "isFunction"),
|
|
63
|
+
function(e) {
|
|
64
|
+
function r(e, o, r) {
|
|
64
65
|
const t = {
|
|
65
|
-
...
|
|
66
|
+
...o
|
|
66
67
|
};
|
|
67
|
-
return ("object" == typeof e && "type" in e && P(e.type) || P(e)) && (t.module =
|
|
68
|
+
return ("object" == typeof e && "type" in e && P(e.type) || P(e)) && (t.module = r),
|
|
68
69
|
t;
|
|
69
70
|
}
|
|
70
|
-
r
|
|
71
|
+
o(r, "forwardPropsWithModule"), e.forwardPropsWithModule = r;
|
|
71
72
|
}(n || (n = {}));
|
|
72
73
|
|
|
73
74
|
var g = p.memo(b);
|
|
74
75
|
|
|
75
|
-
function w(e,
|
|
76
|
-
return
|
|
77
|
-
const t =
|
|
76
|
+
function w(e, o) {
|
|
77
|
+
return r => {
|
|
78
|
+
const t = j(e, r);
|
|
78
79
|
return p.createElement(i.Provider, {
|
|
79
80
|
value: t
|
|
80
81
|
}, p.createElement(g, {
|
|
81
82
|
module: t,
|
|
82
|
-
componentProps:
|
|
83
|
-
component:
|
|
83
|
+
componentProps: r,
|
|
84
|
+
component: o
|
|
84
85
|
}));
|
|
85
86
|
};
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
function b({module: e, component:
|
|
89
|
-
return p.createElement(p.Fragment, null,
|
|
89
|
+
function b({module: e, component: o, componentProps: r}) {
|
|
90
|
+
return p.createElement(p.Fragment, null, o(n.forwardPropsWithModule(o, r, e)));
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
o(w, "provideModuleToComponent"), o(b, "_ComponentRenderer");
|
|
93
94
|
|
|
94
95
|
import { useMemo as E } from "react";
|
|
95
96
|
|
|
96
|
-
import { InjectionProviderModuleError as
|
|
97
|
+
import { InjectionProviderModuleError as T } from "@adimm/x-injection";
|
|
97
98
|
|
|
98
|
-
var
|
|
99
|
+
var x = class e extends T {
|
|
99
100
|
static {
|
|
100
|
-
|
|
101
|
+
o(this, "InjectionHookFactoryError");
|
|
101
102
|
}
|
|
102
103
|
name=e.name;
|
|
103
104
|
};
|
|
104
105
|
|
|
105
|
-
function
|
|
106
|
-
return
|
|
107
|
-
const t = u(), n = E(() => {
|
|
108
|
-
if (0 ===
|
|
109
|
-
return t.getMany(...
|
|
110
|
-
}, [
|
|
106
|
+
function F({use: e, inject: o}) {
|
|
107
|
+
return r => {
|
|
108
|
+
const t = u(), n = E((() => {
|
|
109
|
+
if (0 === o.length) throw new x(t, "The 'deps' property array is missing!");
|
|
110
|
+
return t.getMany(...o);
|
|
111
|
+
}), [ o ]);
|
|
111
112
|
return e({
|
|
112
|
-
...
|
|
113
|
+
...r,
|
|
113
114
|
deps: [ ...n ]
|
|
114
115
|
});
|
|
115
116
|
};
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
|
|
119
|
+
o(F, "hookFactory");
|
|
119
120
|
|
|
120
|
-
export { i as REACT_X_INJECTION_PROVIDER_MODULE_CONTEXT,
|
|
121
|
+
export { i as REACT_X_INJECTION_PROVIDER_MODULE_CONTEXT, F as hookFactory, w as provideModuleToComponent, u as useComponentModule, a as useInject, m as useInjectMany };
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adimm/x-injection-reactjs",
|
|
3
|
+
"private": false,
|
|
4
|
+
"repository": {
|
|
5
|
+
"url": "https://github.com/AdiMarianMutu/x-injection-reactjs"
|
|
6
|
+
},
|
|
3
7
|
"description": "ReactJS integration of the `xInjection` library.",
|
|
4
|
-
"version": "1.0.
|
|
8
|
+
"version": "1.0.4",
|
|
5
9
|
"author": "Adi-Marian Mutu",
|
|
6
10
|
"homepage": "https://github.com/AdiMarianMutu/x-injection-reactjs#readme",
|
|
7
11
|
"bugs": "https://github.com/AdiMarianMutu/x-injection-reactjs/issues",
|
|
@@ -38,7 +42,7 @@
|
|
|
38
42
|
"v:bump-major": "npm version major -m \"chore: update lib major version %s\""
|
|
39
43
|
},
|
|
40
44
|
"dependencies": {
|
|
41
|
-
"@adimm/x-injection": "^
|
|
45
|
+
"@adimm/x-injection": "^3.0.0",
|
|
42
46
|
"react": ">=18.0.0"
|
|
43
47
|
},
|
|
44
48
|
"devDependencies": {
|