@adimm/x-injection-reactjs 1.0.2 → 1.0.3
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 +242 -420
- 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
|
+
# xInjection for React
|
|
16
|
+
|
|
17
|
+
Powerful dependency injection for React components using a modular architecture. Build scalable React applications with clean separation of concerns.
|
|
18
|
+
|
|
15
19
|
## Table of Contents
|
|
16
20
|
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
- [
|
|
20
|
-
- [
|
|
21
|
-
- [Getting Started](#getting-started)
|
|
21
|
+
- [xInjection for React](#xinjection-for-react)
|
|
22
|
+
- [Table of Contents](#table-of-contents)
|
|
23
|
+
- [Overview](#overview)
|
|
24
|
+
- [Installation](#installation)
|
|
22
25
|
- [Quick Start](#quick-start)
|
|
23
|
-
- [
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
- [
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
- [
|
|
33
|
-
|
|
34
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [Contributing](#contributing)
|
|
39
|
-
- [Credits](#credits)
|
|
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)
|
|
35
|
+
- [Module Imports and Exports](#module-imports-and-exports)
|
|
36
|
+
- [Multiple Dependency Injection](#multiple-dependency-injection)
|
|
37
|
+
- [Unit Testing](#unit-testing)
|
|
38
|
+
- [Documentation](#documentation)
|
|
39
|
+
- [Contributing](#contributing)
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
npm i reflect-metadata
|
|
55
|
-
```
|
|
51
|
+
This is the official [ReactJS](https://react.dev/) implementation of [xInjection](https://github.com/AdiMarianMutu/x-injection).
|
|
56
52
|
|
|
57
|
-
|
|
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`
|
|
66
|
-
|
|
67
|
-
### TypeScript Configuration
|
|
59
|
+
**TypeScript Configuration**
|
|
68
60
|
|
|
69
|
-
Add
|
|
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,327 @@ 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.
|
|
102
|
+
## Core Concepts
|
|
115
103
|
|
|
116
|
-
### Component
|
|
104
|
+
### Component Modules
|
|
117
105
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
`user-dashboard/user-dashboard.module.ts`
|
|
106
|
+
Create a module blueprint that defines your component's dependencies:
|
|
121
107
|
|
|
122
108
|
```ts
|
|
109
|
+
// user-dashboard.module.ts
|
|
123
110
|
export const UserDashboardModuleBp = ProviderModule.blueprint({
|
|
124
|
-
id: '
|
|
125
|
-
|
|
111
|
+
id: 'UserDashboardModule',
|
|
112
|
+
providers: [UserService],
|
|
113
|
+
exports: [UserService],
|
|
126
114
|
});
|
|
127
115
|
```
|
|
128
116
|
|
|
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.
|
|
132
|
-
|
|
133
|
-
### Component Service
|
|
117
|
+
**Blueprint vs Module:** Use a **blueprint** for reusable components (multiple instances), use a raw **module** for singleton components (single instance).
|
|
134
118
|
|
|
135
|
-
|
|
119
|
+
### Services
|
|
136
120
|
|
|
137
|
-
`
|
|
121
|
+
Define services using the `@Injectable()` decorator:
|
|
138
122
|
|
|
139
123
|
```ts
|
|
124
|
+
// user-dashboard.service.ts
|
|
140
125
|
@Injectable()
|
|
141
126
|
export class UserDashboardService {
|
|
142
127
|
firstName: string;
|
|
143
128
|
lastName: string;
|
|
129
|
+
|
|
130
|
+
getFullName() {
|
|
131
|
+
return `${this.firstName} ${this.lastName}`;
|
|
132
|
+
}
|
|
144
133
|
}
|
|
145
134
|
```
|
|
146
135
|
|
|
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?
|
|
136
|
+
### Dependency Injection
|
|
152
137
|
|
|
153
|
-
|
|
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`.
|
|
138
|
+
Use `useInject` to access services in your components:
|
|
172
139
|
|
|
173
140
|
```tsx
|
|
174
|
-
const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, (
|
|
175
|
-
|
|
176
|
-
});
|
|
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
|
-
|
|
183
|
-
#### Override the entire Child Module
|
|
184
|
-
|
|
185
|
-
Let's say that the `Child` component has this `module`:
|
|
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],
|
|
202
|
-
});
|
|
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
|
-
|
|
211
|
-
return <ChildComponent module={module} />;
|
|
212
|
-
});
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Now the `ChildComponent` will be instantiated with the `module` received from the `ParentComponent`, therefore it'll use the `ChildService` managed into the `ParentModule`.
|
|
216
|
-
|
|
217
|
-
> [!Tip]
|
|
218
|
-
>
|
|
219
|
-
> This is perfect to use when you are writing _unit tests_ and you want to mock an entire component `module`
|
|
220
|
-
|
|
221
|
-
#### Override only specific Child Providers
|
|
222
|
-
|
|
223
|
-
> This is the approach which you should strive to use most of the times as it is less prone to _"human error"_ than overriding the entire module.
|
|
224
|
-
|
|
225
|
-
Let's re-use the same example as the one from the above, the `ParentModule`:
|
|
226
|
-
|
|
227
|
-
```ts
|
|
228
|
-
const ParentModuleBp = ProviderModule.blueprint({
|
|
229
|
-
id: 'ComponentParentModule',
|
|
230
|
-
providers: [ParentService, ChildService],
|
|
231
|
-
exports: [ParentService, ChildService],
|
|
232
|
-
});
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
And now the rendering part:
|
|
236
|
-
|
|
237
|
-
```ts
|
|
238
|
-
const ParentComponent = provideModuleToComponent(ParentModuleBp, () => {
|
|
239
|
-
// notice that we are not using the `module` prop anymore.
|
|
240
|
-
const childService = useInject(ChildService);
|
|
141
|
+
const UserDashboard = provideModuleToComponent(UserDashboardModuleBp, () => {
|
|
142
|
+
const userService = useInject(UserService);
|
|
241
143
|
|
|
242
|
-
return <
|
|
144
|
+
return <div>{userService.getFullName()}</div>;
|
|
243
145
|
});
|
|
244
146
|
```
|
|
245
147
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
> [!Note]
|
|
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.
|
|
251
|
-
|
|
252
|
-
### Hook Injection
|
|
253
|
-
|
|
254
|
-
You already have seen in action the low-level [useInject](https://adimarianmutu.github.io/x-injection-reactjs/functions/useInject.html) hook _(take a look also at the [useInjectMany](https://adimarianmutu.github.io/x-injection-reactjs/functions/useInjectMany.html) hook)_. It is quite useful when you just have to inject quickly some dependencies into a component quite simple.
|
|
148
|
+
### Custom Hooks
|
|
255
149
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
This means that we can actually use the [hookFactory](https://adimarianmutu.github.io/x-injection-reactjs/functions/hookFactory.html) method to compose a _custom_ hook with access to any dependency available in the component module.
|
|
150
|
+
Create reusable hooks with dependency injection using `hookFactory`:
|
|
259
151
|
|
|
260
152
|
```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]>) => {
|
|
153
|
+
const useUserFullName = hookFactory({
|
|
154
|
+
use: ({ firstName, lastName, deps: [userService] }) => {
|
|
268
155
|
userService.firstName = firstName;
|
|
269
156
|
userService.lastName = lastName;
|
|
270
|
-
|
|
271
|
-
return userService.generateFullName();
|
|
157
|
+
return userService.getFullName();
|
|
272
158
|
},
|
|
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
159
|
inject: [UserService],
|
|
278
160
|
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
Now you can use it in inside any component which is using a `module` which can provide the `UserService`.
|
|
282
|
-
|
|
283
|
-
```tsx
|
|
284
|
-
export function UserInfo({ firstName, lastName }: UserInfoProps) {
|
|
285
|
-
const userFullName = useGenerateFullName({ firstName, lastName });
|
|
286
161
|
|
|
287
|
-
|
|
288
|
-
}
|
|
162
|
+
// Use in any component
|
|
163
|
+
const fullName = useUserFullName({ firstName: 'John', lastName: 'Doe' });
|
|
289
164
|
```
|
|
290
165
|
|
|
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
166
|
## Examples
|
|
296
167
|
|
|
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))_
|
|
168
|
+
### Zustand Integration
|
|
300
169
|
|
|
301
|
-
|
|
170
|
+
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.
|
|
302
171
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
- An `inputbox`
|
|
306
|
-
- A `list viewer`
|
|
307
|
-
- A `dropdown`
|
|
308
|
-
- An `autocomplete`
|
|
309
|
-
|
|
310
|
-
<hr>
|
|
172
|
+
```ts
|
|
173
|
+
// counter.service.ts
|
|
311
174
|
|
|
312
|
-
|
|
175
|
+
import { Injectable } from '@adimm/x-injection-reactjs';
|
|
176
|
+
import { create } from 'zustand';
|
|
313
177
|
|
|
314
|
-
|
|
178
|
+
interface CounterStore {
|
|
179
|
+
count: number;
|
|
180
|
+
increment: () => void;
|
|
181
|
+
decrement: () => void;
|
|
182
|
+
reset: () => void;
|
|
183
|
+
}
|
|
315
184
|
|
|
316
|
-
```ts
|
|
317
185
|
@Injectable()
|
|
318
|
-
export class
|
|
319
|
-
|
|
186
|
+
export class CounterService {
|
|
187
|
+
// Store instance encapsulated within the service
|
|
188
|
+
private readonly store = create<CounterStore>((set) => ({
|
|
189
|
+
count: 0,
|
|
190
|
+
increment: () => set((state) => ({ count: state.count + 1 })),
|
|
191
|
+
decrement: () => set((state) => ({ count: state.count - 1 })),
|
|
192
|
+
reset: () => set({ count: 0 }),
|
|
193
|
+
}));
|
|
194
|
+
|
|
195
|
+
// Expose store hook for components to subscribe
|
|
196
|
+
get useStore() {
|
|
197
|
+
return this.store;
|
|
198
|
+
}
|
|
320
199
|
|
|
321
|
-
//
|
|
322
|
-
|
|
200
|
+
// Getter to access current state from within the service
|
|
201
|
+
private get storeState() {
|
|
202
|
+
return this.store.getState();
|
|
203
|
+
}
|
|
323
204
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
this.
|
|
205
|
+
// Business logic methods
|
|
206
|
+
increment() {
|
|
207
|
+
this.storeState.increment();
|
|
208
|
+
}
|
|
327
209
|
|
|
328
|
-
|
|
210
|
+
decrement() {
|
|
211
|
+
this.storeState.decrement();
|
|
329
212
|
}
|
|
330
|
-
}
|
|
331
213
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
exports: [InputboxService],
|
|
336
|
-
});
|
|
337
|
-
```
|
|
214
|
+
reset() {
|
|
215
|
+
this.storeState.reset();
|
|
216
|
+
}
|
|
338
217
|
|
|
339
|
-
|
|
218
|
+
incrementBy(amount: number) {
|
|
219
|
+
// Complex logic lives in the service
|
|
220
|
+
const currentCount = this.storeState.count;
|
|
221
|
+
this.store.setState({ count: currentCount + amount });
|
|
222
|
+
}
|
|
340
223
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
224
|
+
async incrementAsync() {
|
|
225
|
+
// Handle async operations in the service
|
|
226
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
227
|
+
this.increment();
|
|
228
|
+
}
|
|
344
229
|
}
|
|
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
230
|
```
|
|
358
231
|
|
|
359
|
-
<hr>
|
|
360
|
-
|
|
361
|
-
> Listview
|
|
362
|
-
|
|
363
|
-
`listview.service.ts`
|
|
364
|
-
|
|
365
232
|
```ts
|
|
366
|
-
|
|
367
|
-
export class ListviewService {
|
|
368
|
-
items = [];
|
|
233
|
+
// counter.module.ts
|
|
369
234
|
|
|
370
|
-
|
|
371
|
-
}
|
|
235
|
+
import { ProviderModule } from '@adimm/x-injection-reactjs';
|
|
372
236
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
237
|
+
import { CounterService } from './counter.service';
|
|
238
|
+
|
|
239
|
+
export const CounterModuleBp = ProviderModule.blueprint({
|
|
240
|
+
id: 'CounterModule',
|
|
241
|
+
providers: [CounterService],
|
|
242
|
+
exports: [CounterService],
|
|
377
243
|
});
|
|
378
244
|
```
|
|
379
245
|
|
|
380
|
-
`listview.tsx`
|
|
381
|
-
|
|
382
246
|
```tsx
|
|
383
|
-
|
|
384
|
-
items: any[];
|
|
385
|
-
}
|
|
247
|
+
// counter.component.tsx
|
|
386
248
|
|
|
387
|
-
|
|
388
|
-
const service = useInject(ListviewService);
|
|
249
|
+
import { provideModuleToComponent, useInject } from '@adimm/x-injection-reactjs';
|
|
389
250
|
|
|
390
|
-
|
|
251
|
+
import { CounterModuleBp } from './counter.module';
|
|
252
|
+
import { CounterService } from './counter.service';
|
|
253
|
+
|
|
254
|
+
const Counter = provideModuleToComponent(CounterModuleBp, () => {
|
|
255
|
+
// Inject service for business logic
|
|
256
|
+
const counterService = useInject(CounterService);
|
|
257
|
+
|
|
258
|
+
// Subscribe to store for reactive state
|
|
259
|
+
const count = counterService.useStore((state) => state.count);
|
|
391
260
|
|
|
392
261
|
return (
|
|
393
262
|
<div>
|
|
394
|
-
{
|
|
395
|
-
|
|
396
|
-
))}
|
|
263
|
+
<h2>Count: {count}</h2>
|
|
264
|
+
<button onClick={() => counterService.increment()}>+1</button>
|
|
265
|
+
<button onClick={() => counterService.decrement()}>-1</button>
|
|
266
|
+
<button onClick={() => counterService.incrementBy(5)}>+5</button>
|
|
267
|
+
<button onClick={() => counterService.incrementAsync()}>+1 Async</button>
|
|
268
|
+
<button onClick={() => counterService.reset()}>Reset</button>
|
|
397
269
|
</div>
|
|
398
270
|
);
|
|
399
271
|
});
|
|
272
|
+
|
|
273
|
+
export default Counter;
|
|
400
274
|
```
|
|
401
275
|
|
|
402
|
-
|
|
276
|
+
**Key Benefits:**
|
|
403
277
|
|
|
404
|
-
|
|
278
|
+
- **Encapsulation**: Store is encapsulated within the service, not exposed globally
|
|
279
|
+
- **Separation of concerns**: Business logic in services, UI only subscribes to state
|
|
280
|
+
- **Testability**: Services are self-contained and easy to test
|
|
281
|
+
- **Reusability**: Services with stores can be shared across components via dependency injection
|
|
282
|
+
- **Type safety**: Full TypeScript support throughout
|
|
405
283
|
|
|
406
|
-
|
|
284
|
+
### Parent-Child Provider Control
|
|
407
285
|
|
|
408
|
-
`
|
|
286
|
+
Parent components can control child component dependencies using the `inject` prop:
|
|
409
287
|
|
|
410
288
|
```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
|
-
],
|
|
289
|
+
// Child module and service
|
|
290
|
+
const ChildModuleBp = ProviderModule.blueprint({
|
|
291
|
+
id: 'ChildModule',
|
|
292
|
+
providers: [ChildService],
|
|
293
|
+
exports: [ChildService],
|
|
433
294
|
});
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
`dropdown.tsx`
|
|
437
|
-
|
|
438
|
-
```tsx
|
|
439
|
-
export interface DropdownProps {
|
|
440
|
-
listviewProps: ListviewProps;
|
|
441
295
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const service = useInject(DropdownService);
|
|
449
|
-
|
|
450
|
-
/* Remaining fancy implementation */
|
|
296
|
+
// Parent module
|
|
297
|
+
const ParentModuleBp = ProviderModule.blueprint({
|
|
298
|
+
id: 'ParentModule',
|
|
299
|
+
providers: [ParentService, ChildService],
|
|
300
|
+
exports: [ParentService, ChildService],
|
|
301
|
+
});
|
|
451
302
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
303
|
+
// Parent component controls child's service
|
|
304
|
+
const ParentComponent = provideModuleToComponent(ParentModuleBp, () => {
|
|
305
|
+
const childService = useInject(ChildService);
|
|
455
306
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
</div>
|
|
460
|
-
);
|
|
461
|
-
}
|
|
462
|
-
);
|
|
307
|
+
// Override child's ChildService with parent's instance
|
|
308
|
+
return <ChildComponent inject={[{ provide: ChildService, useValue: childService }]} />;
|
|
309
|
+
});
|
|
463
310
|
```
|
|
464
311
|
|
|
465
|
-
|
|
312
|
+
This pattern is useful for:
|
|
466
313
|
|
|
467
|
-
|
|
314
|
+
- Building composable component hierarchies
|
|
315
|
+
- Sharing state between parent and child components
|
|
316
|
+
- Creating flexible component APIs
|
|
468
317
|
|
|
469
|
-
|
|
318
|
+
## Advanced Usage
|
|
470
319
|
|
|
471
|
-
|
|
320
|
+
### Module Imports and Exports
|
|
472
321
|
|
|
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
|
-
}
|
|
322
|
+
Modules can import and re-export other modules:
|
|
491
323
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
324
|
+
```ts
|
|
325
|
+
const DropdownModuleBp = ProviderModule.blueprint({
|
|
326
|
+
id: 'DropdownModule',
|
|
327
|
+
imports: [ListviewModuleBp], // Import ListviewModule
|
|
328
|
+
providers: [DropdownService],
|
|
329
|
+
exports: [
|
|
330
|
+
ListviewModuleBp, // Re-export imported module
|
|
331
|
+
DropdownService,
|
|
332
|
+
],
|
|
501
333
|
});
|
|
502
334
|
```
|
|
503
335
|
|
|
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);
|
|
516
|
-
|
|
517
|
-
service.inputboxService.currentValue = currentText;
|
|
336
|
+
### Multiple Dependency Injection
|
|
518
337
|
|
|
519
|
-
|
|
520
|
-
// Produces: [29, 9, 1969]
|
|
338
|
+
Use `useInjectMany` to inject multiple dependencies:
|
|
521
339
|
|
|
522
|
-
|
|
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
|
-
);
|
|
340
|
+
```ts
|
|
341
|
+
const [userService, apiService] = useInjectMany([UserService, ApiService]);
|
|
533
342
|
```
|
|
534
343
|
|
|
535
|
-
|
|
344
|
+
## Unit Testing
|
|
536
345
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
It is very easy to create mock modules so you can provide them to your components in your unit tests.
|
|
346
|
+
Mock modules easily for testing:
|
|
540
347
|
|
|
541
348
|
```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
|
-
}
|
|
349
|
+
import { act, render } from '@testing-library/react';
|
|
549
350
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const ApiModuleBp = new ProviderModule.blueprint({
|
|
351
|
+
// Original module
|
|
352
|
+
const ApiModuleBp = ProviderModule.blueprint({
|
|
554
353
|
id: 'ApiModule',
|
|
555
354
|
providers: [UserService, ApiService],
|
|
556
355
|
});
|
|
557
356
|
|
|
558
|
-
//
|
|
357
|
+
// Create mocked version
|
|
559
358
|
const ApiModuleBpMocked = ApiModuleBp.clone().updateDefinition({
|
|
560
359
|
id: 'ApiModuleMocked',
|
|
561
360
|
providers: [
|
|
562
|
-
{
|
|
563
|
-
provide: UserService,
|
|
564
|
-
useClass: UserService_Mock,
|
|
565
|
-
},
|
|
361
|
+
{ provide: UserService, useClass: UserServiceMock },
|
|
566
362
|
{
|
|
567
363
|
provide: ApiService,
|
|
568
364
|
useValue: {
|
|
569
|
-
sendRequest:
|
|
570
|
-
console.log(location);
|
|
571
|
-
},
|
|
365
|
+
sendRequest: vi.fn().mockResolvedValue({ data: 'mocked' }),
|
|
572
366
|
},
|
|
573
367
|
},
|
|
574
368
|
],
|
|
575
369
|
});
|
|
576
370
|
|
|
577
|
-
//
|
|
578
|
-
await act(async () => render(<
|
|
371
|
+
// Test with mocked module
|
|
372
|
+
await act(async () => render(<MyComponent module={ApiModuleBpMocked} />));
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Testing with Zustand:**
|
|
376
|
+
|
|
377
|
+
```tsx
|
|
378
|
+
import { act, renderHook } from '@testing-library/react';
|
|
379
|
+
|
|
380
|
+
import { CounterService } from './counter.service';
|
|
381
|
+
|
|
382
|
+
it('should increment counter via service', () => {
|
|
383
|
+
const service = new CounterService();
|
|
384
|
+
|
|
385
|
+
const { result } = renderHook(() => service.useStore((s) => s.count));
|
|
386
|
+
|
|
387
|
+
expect(result.current).toBe(0);
|
|
388
|
+
|
|
389
|
+
act(() => {
|
|
390
|
+
service.increment();
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
expect(result.current).toBe(1);
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should handle complex business logic', () => {
|
|
397
|
+
const service = new CounterService();
|
|
398
|
+
|
|
399
|
+
act(() => {
|
|
400
|
+
service.incrementBy(10);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
expect(service.useStore.getState().count).toBe(10);
|
|
404
|
+
});
|
|
579
405
|
```
|
|
580
406
|
|
|
581
407
|
## Documentation
|
|
582
408
|
|
|
583
|
-
|
|
409
|
+
📚 **Full API Documentation:** [https://adimarianmutu.github.io/x-injection-reactjs](https://adimarianmutu.github.io/x-injection-reactjs/index.html)
|
|
584
410
|
|
|
585
|
-
|
|
411
|
+
For more information about the base library, see [xInjection Documentation](https://github.com/AdiMarianMutu/x-injection#readme).
|
|
586
412
|
|
|
587
413
|
## Contributing
|
|
588
414
|
|
|
589
|
-
Pull requests are
|
|
590
|
-
|
|
591
|
-
Please ensure your contributions adhere to the project's code style. See the repository for more details.
|
|
415
|
+
Pull requests are welcome! Please ensure your contributions follow the project's code style.
|
|
592
416
|
|
|
593
|
-
##
|
|
417
|
+
## License
|
|
594
418
|
|
|
595
|
-
|
|
419
|
+
MIT © [Adi-Marian Mutu](https://www.linkedin.com/in/mutu-adi-marian/)
|
|
596
420
|
|
|
597
421
|
---
|
|
598
422
|
|
|
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.**
|
|
423
|
+
**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.3",
|
|
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": {
|