{"_id":"@symplx/inject-react","_rev":"26-78c902946cc8e756da2477b1358c0eb2","name":"@symplx/inject-react","dist-tags":{"latest":"0.1.0-beta.3"},"versions":{"0.1.0-beta.3":{"name":"@symplx/inject-react","version":"0.1.0-beta.3","keywords":["di","ioc","dependency","injection","inversion","of","control","react"],"author":{"name":"Oleg Volovikov"},"license":"MIT","_id":"@symplx/inject-react@0.1.0-beta.3","maintainers":[{"name":"oleg.wx","email":"oleg.wx@gmail.com"}],"homepage":"https://github.com/oleg-wx/simply-inject-react#readme","bugs":{"url":"https://github.com/oleg-wx/simply-inject-react/issues"},"dist":{"shasum":"7a789f19a9dcf1ecf4a9a4c0c24af1c75325b931","tarball":"https://registry.npmjs.org/@symplx/inject-react/-/inject-react-0.1.0-beta.3.tgz","fileCount":21,"integrity":"sha512-BLiVxkZdkySdZWcgEkxZKEtlMjYJ1Zogae8BgzfEfuxH+FOxdLV/xeypRyEpfjT6sVxKvHA2KhnvKPlzPdIqMQ==","signatures":[{"sig":"MEUCIQDiqFy/ZeEfuc8pXDbqsCGJAWTwWJF3yv+/CgkQ/1JUegIgJdrR2E9NKHKaZoIzhRw09gbkLKbmME9UMklX5omhe2M=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":278603},"main":"dist/index.js","types":"./dist/index.d.ts","module":"dist/index.esm.js","gitHead":"c3e58a9ceb24ac5696afaec339e3d5e02dac38dc","scripts":{"build":"rollup -c --bundleConfigAsCjs"},"_npmUser":{"name":"oleg.wx","email":"oleg.wx@gmail.com"},"repository":{"url":"git+https://github.com/oleg-wx/simply-inject-react.git","type":"git"},"_npmVersion":"9.5.1","description":"Simplest DI for React","directories":{},"_nodeVersion":"18.16.0","dependencies":{"react":">=16.x.x","react-dom":">=16.x.x"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tslib":"^2.5.3","rollup":"^3.25.1","typescript":"^5.1.3","web-vitals":"^2.1.4","@types/node":"^20.3.1","rollup-jest":"^3.1.0","@types/react":"^18.2.12","@types/react-dom":"^18.2.5","rollup-plugin-dts":"^5.3.0","@rollup/plugin-terser":"^0.4.3","@rollup/plugin-commonjs":"^25.0.1","@rollup/plugin-typescript":"^11.1.1","@rollup/plugin-node-resolve":"^15.1.0","rollup-plugin-peer-deps-external":"^2.2.4"},"peerDependencies":{"reflect-metadata":"^0.1.x"},"_npmOperationalInternal":{"tmp":"tmp/inject-react_0.1.0-beta.3_1687548164474_0.04135773401258236","host":"s3://npm-registry-packages"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."}},"time":{"created":"2023-06-23T19:22:44.414Z","modified":"2025-04-09T14:31:49.229Z","0.0.1-alpha.1":"2023-06-20T19:41:35.326Z","0.0.1-alpha.2":"2023-06-20T20:58:19.274Z","0.0.1-alpha.3":"2023-06-20T21:05:15.611Z","0.0.1-alpha.4":"2023-06-21T01:30:57.028Z","0.0.1-alpha.5":"2023-06-21T01:39:24.899Z","0.0.1-alpha.6":"2023-06-21T13:33:36.753Z","0.0.1-alpha.7":"2023-06-22T15:13:16.809Z","0.0.1-beta.1":"2023-06-22T23:47:41.565Z","0.0.1-beta.2":"2023-06-22T23:58:41.285Z","0.1.0-beta.1":"2023-06-23T19:18:44.086Z","0.1.0-beta.2":"2023-06-23T19:20:48.744Z","0.1.0-beta.3":"2023-06-23T19:22:44.803Z"},"bugs":{"url":"https://github.com/oleg-wx/simply-inject-react/issues"},"author":{"name":"Oleg Volovikov"},"license":"MIT","homepage":"https://github.com/oleg-wx/simply-inject-react#readme","keywords":["di","ioc","dependency","injection","inversion","of","control","react"],"repository":{"url":"git+https://github.com/oleg-wx/simply-inject-react.git","type":"git"},"description":"Simplest DI for React","maintainers":[{"name":"oleg.wx","email":"oleg.wx@gmail.com"}],"readme":"# Simply Inject for React\n\nSimplest **dependency injection** for **React** using _Context_. Nothing fancy...  \n_[Typescript support]_  \n_[React 16.+]_\n\n#### (v0.1.0.beta)\n\n### Install\n\n```\nnpm i @symplx/inject-react\n```\n\n### Requirements\n\nIf you want to utilize **decorators** and especially **Injections** capability it will be necessary to enable support for them. If you choose not to use **decorators**, you can still manage your dependencies using [**factories**](#factory).\n\n**React** _create-react-app_ and _react-scripts_ configurations do not natively support **Decorators**. However, there are some plugins available that can help you incorporate this functionality into your project.\n\nTo begin, I recommend using the `babel-plugin-transform-typescript-metadata` plugin, as it enables the parsing of decorators using the \"@\" symbol. Additionally, ensure that the `experimentalDecorators` and `emitDecoratorMetadata` options in your tsconfig.json file are set to true.\n\nIn order to fully leverage decorators, it may be beneficial to include the `@babel/plugin-proposal-decorators` and `@babel/plugin-proposal-class-properties` plugins. These will provide additional support and capabilities for working with decorators in your codebase.  \nFurthermore, if you'd like to enable _path aliases_, allowing for more concise import statements, you'll need to make adjustments to the webpack configuration without _ejecting_ the application. One option I personally recommend which allows you to customize the `create-react-app` configuration is using `react-app-rewired` with `customize-cra` but there are other options...\n\n```\nnpm i -D react-app-rewired customize-cra babel-plugin-transform-typescript-metadata @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties\n```\n\n```javascript\n// config-overrides.js\nconst path = require('path');\nconst { useBabelRc, override, addWebpackAlias } = require('customize-cra');\n\nmodule.exports = override(useBabelRc());\n```\n\n```javascript\n// .babelrc.js\nmodule.exports = {\n  plugins: [\n    'babel-plugin-transform-typescript-metadata',\n    ['@babel/plugin-proposal-decorators', { legacy: true }],\n    ['@babel/plugin-proposal-class-properties', { loose: true }],\n  ],\n};\n```\n\nTo enable **decorators** in your application, you need to add support for `reflect-metadata`. This can be done by including the following line at the beginning of your index.tsx file:\n\n```typescript\n// index.tsx\nimport 'reflect-metadata';\n```\n\nBy importing the `reflect-metadata` package, you ensure that the necessary metadata reflection capabilities are available to support decorators throughout your application.\nMake sure to add this import statement at the top of your index.tsx file, before any other code, to ensure that the metadata support is initialized from the beginning.\n\n### Decorators\n\n### Basics\n\nTo add dependency injection and resolve implementations, you should wrap your components with a `DependencyProvider` and provide the necessary dependencies. Here's an example of how you can achieve this:\n\n```jsx\n// App.tsx\n<DependencyProvider provide={[provideClass(MyServiceAbstract, MyServiceConcrete)]}>\n  {/* Components that will use Provider */}\n  <SomeComponent />\n</DependencyProvider>\n```\n\nTo resolve the dependency, you can utilize the `useResolver` hook. This hook already includes memoization functionality, ensuring efficient dependency resolution. Make sure to pass the dependencies array as the last parameter. This will ensure that the resolver function is re-evaluated only when the dependencies change, optimizing performance.\n\n```jsx\n// SomeComponent.tsx\nconst myService = useResolver(MyServiceAbstract);\nreturn <>{myService.value}</>;\n```\n\n```typescript\n// MyService.ts\nexport abstract class MyServiceAbstract {\n  value?: string;\n}\n\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  value = 'Hello Service';\n}\n```\n\nYou have the flexibility to nest `DependencyProvider` within each other to override dependencies. When a child `DependencyProvider` is nested inside a parent, it has access to the dependencies provided by the parent. This allows you to selectively override specific dependencies at different levels of your component tree, providing a granular control over dependency injection.\n\n### Providers\n\nIn your dependency injection setup, you have the flexibility to provide implementations using **classes**, **factories**, or **values**\n\n```typescript\nprovideClass(MyServiceAbstract, MyServiceConcrete);\n\nprovideFactory(MyServiceAbstract, ()=>new MyServiceConcrete());\n\nconst SERVICE = new StaticKey<IService>(\"SERVICE\");\nprovideValue(SERVICE, { ... });\n```\n\n### Lifetime\n\nThere are four different lifetimes available for managing the lifespan of dependencies:\n\n1. **Scoped** (default): Scoped lifetime means that a single instance of the dependency is created and shared within the scope of the `DependencyProvider`, all dependencies within that scope will share the same instance.\n\n2. **Transient**: Transient lifetime creates a new instance of the dependency every time it is requested. Each time a dependency is resolved, a new instance is created, ensuring a fresh copy of the dependency is used.\n\n3. **Looped**: Looped lifetime is similar to transient, but it is scoped for one event loop. It means that within the same event loop, the same instance of the dependency will be reused. However, when the event loop ends, the dependency will be disposed, and a new instance will be created in the next event loop if needed.\n\n4. **Singleton**: Singleton lifetime ensures that only one instance of the dependency is created throughout the entire application. It can be added only once in any `DependencyProvider` and is shared across all parts of the application. It is recommended to add the Singleton scope to the top-most parent when configuring your dependency injection setup.\n\nIt is **important** to be **cautious** when injecting services with shorter lifecycles into those with longer lifecycles. When a service with a shorter lifespan is injected into a service with a longer lifespan, it can lead to unexpected behavior or memory leaks.\n\n```typescript\nprovideClass(MyServiceAbstract, MyServiceConcrete, 'transient');\n```\n\n### Injections\n\n```typescript\n// MyService.ts\nexport abstract class SomeStrategyAbstract {}\n\n@Injectable()\nexport class SomeStrategyConcrete {}\n\nexport abstract class MyServiceAbstract {}\n\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  constructor(public strategy: SomeStrategyAbstract) {}\n}\n```\n\nYou might specify direct dependency with `@Inject`, without it injection will be provided automatically by _metadata_ so for interfaces and object it is mandatory to use `@Inject` with `StaticKey`.\n\nWhen using dependency injection in your application, you have the option to specify a direct dependency using the `@Inject` decorator. By using `@Inject`, you explicitly declare the dependency and indicate that it should be injected into the corresponding component or service.  \nUse `StaticKey` to represent an interface or object, you would typically use the `@Inject` decorator to specify the dependency and ensure that the correct implementation is injected.\n\n```typescript\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  constructor(@Inject(SomeStrategyAbstract) public strategy: SomeStrategyAbstract) {}\n}\n```\n\n```typescript\nexport const RESOURCE_URL = new StaticKey()<string>('RESOURCE_URL');\n\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  constructor(@Inject(RESOURCE_URL) public url: string) {}\n}\n```\n\n### Factory\n\nFactories in the context of dependency injection allow you to create dependencies by calling a factory method. Instead of directly providing an instance of a class or a value, you provide a factory function that is responsible for creating the instance.  \nThe factory method receives a resolve function as an argument. This resolve function allows the factory to request and obtain other dependencies needed to construct the desired object.\n\n```jsx\n<DependencyProvider provide={[provideFactory(MyServiceConcrete, () => new MyServiceConcrete())]}></DependencyProvider>\n\n<DependencyProvider provide={[provideFactory(MyServiceConcrete, (resolve) => new MyServiceConcrete(resolve(MY_DEPENDENCY)))]}></DependencyProvider>\n```\n\nMoreover if you prefer not to use metadata, configure webpack loaders, or make additional modifications, using factories is your way to go!\n\n[See](#factory-example)\n\n### Resolutions\n\nWhen resolving dependencies using dependency injection, you have several options to control how dependencies are resolved within the dependency hierarchy.\n\n1. **skipSelf**: This resolution strategy instructs the dependency injection to skip the closest `DependencyProvider` and look for the dependency in the next available provider in the hierarchy. It allows you to bypass the immediate provider and access a dependency from a higher-level provider.\n\n2. **onlySelf**: With this resolution strategy, the dependency injection will only consider dependencies from the closest `DependencyProvider`. It restricts the resolution to the immediate provider and prevents the framework from searching for dependencies further up the hierarchy.\n\n3. **default**: The default resolution strategy instructs the framework to perform a lookup for the dependency in every `DependencyProvider` in the upward hierarchy.\n\nAdditionally, **singletons**, by their nature, they ignore resolution strategies and retrieve their dependencies global app-level container, disregarding any specific resolution requests.\n\n```typescript\nconst skipSelf = useResolver(MyServiceAbstract, 'skipSelf');\nconst onlySelf = useResolver(MyServiceAbstract, 'onlySelf');\n```\n\n```typescript\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  constructor(@Resolution('onlySelf') public strategy: SomeStrategyAbstract) {}\n}\n```\n\n**Avoid** circular dependencies with `transient` and `skipSelf` as it will end up with **Maximum call stack size exceeded** Error. (Yet there is no self explaining error)\n\n### Required\n\nBy default not provided dependencies will re resolved as `undefined`, but constructor arguments may be marked as required to _throw an error_ if dependency is not provided.\n\nEnsure to provide all required dependencies to avoid runtime errors. By default, if a dependency is not provided, it will be resolved as `undefined`. However, you can mark constructor arguments as `@Required`, which will throw an error if a required dependency is not provided. This helps in early detection of missing dependencies during development.\n\n```typescript\n@Injectable()\nexport class MyServiceConcrete extends MyServiceAbstract {\n  constructor(@Required() public strategy: SomeStrategyAbstract) {}\n}\n```\n\n### Simple Example\n\n```jsx\n// Preview.tsx\nexport function Preview() {\n  return (\n    <DependencyProvider\n      provide={[\n        /*  RandomNameService is a transient because we need to reassign its constructor parameter,\n            but we won't provide it once more but memoize it for component*/\n        provideClass(RandomNameService, 'transient'),\n        provideClass(Formatter, FormatterUppercase),\n        provideValue(NAME_URL, 'https://randomuser.me/api/'),\n        /* NAME_GETTER could be value as well, just for the demo purposes lets make it singleton factory */\n        provideFactory(\n          NAME_GETTER,\n          () => ({\n            getName: (value: { first: string, last: string, title?: string }) =>\n              [value.title, value.first, value.last].filter((x) => x).join(' '),\n          }),\n          'singleton'\n        ),\n      ]}\n    >\n      <SomeNameComponent />\n    </DependencyProvider>\n  );\n}\n```\n\n```jsx\n// SomeNameComponent.tsx\nexport function SomeNameContainer() {\n  const [name, setName] = useState(\"\");\n  const test = useResolver(RandomNameService, [])!;\n\n  useEffect(() => {\n    test.getName().then((name) => setName(name));\n  }, [test]);\n\n  return (\n    /*  We are reusing parent Resolver with its implementations, but we override Formatter.\n        As Formatter is scoped and used as constructor argument we made service that use it - transient.\n    */\n    <DependencyProvider provide={[provideClass(Formatter, FormatterLowercase)]}>\n      <b>{name}</b>\n      <SomeOtherNameContainer />\n    </DependencyProvider>\n  );\n}\n```\n\n```jsx\n// SomeOtherNameComponent.tsx\nexport function SomeOtherNameComponent() {\n  const [name, setName] = useState(\"\");\n\n  // we resolve new service (as it is transient) with new formatter\n  const test = useResolver(RandomNameService, [])!;\n\n  useEffect(() => {\n    test.getName().then((name) => setName(name));\n  }, [test]);\n\n  return <i>{name}</i>;\n}\n```\n\n```typescript\nexport const NAME_URL = new StaticKey()<string>('NAMES_URL');\nexport const NAME_GETTER = new StaticKey()<NameGetter>('NAME_GETTER');\n\nexport interface NameGetter {\n  getName(value: unknown): string;\n}\n```\n\n```typescript\n@Injectable()\nexport class RandomNameService {\n  constructor(\n    protected formatter: Formatter,\n    @Inject(NAME_GETTER) private nameGetter: NameGetter,\n    @Inject(NAME_URL) private url: string\n  ) {}\n\n  async getName(): Promise<string> {\n    const response = await fetch(this.url);\n    const value = await response.json();\n    const name = this.nameGetter.getName(value.results[0].name);\n\n    return this.formatter.format(name);\n  }\n}\n```\n\n```typescript\nexport abstract class Formatter {\n  abstract format(value: string): string;\n}\n\n@Injectable()\nexport class FormatterUppercase extends Formatter {\n  format(value: string): string {\n    return value.toUpperCase();\n  }\n}\n\n@Injectable()\nexport class FormatterLowercase extends Formatter {\n  format(value: string): string {\n    return value.toLowerCase();\n  }\n}\n```\n\n### Factory Example\n\nBased on [Simple Example](#simple-example) lets change some parts to let you use `DependencyProvider` without adding plugins or ejecting the app.\n\nRemove decorators:\n\n```typescript\nexport class RandomNameService {\n  constructor(protected formatter: Formatter, private nameGetter: NameGetter, private url: string) {}\n  // same getName() method\n}\nexport class FormatterUppercase extends Formatter {\n  /*original*/\n}\nexport class FormatterLowercase extends Formatter {\n  /*original*/\n}\n```\n\nNow lets change only `DependencyProvider`s:\n\n```jsx\n// Preview.tsx\n<DependencyProvider provide={[\n  /* replace Class providers with Factory using resolve method */\n  provideFactory(\n    RandomNameService,\n    /* resolve function will resolve dependency */\n    (resolve) => new RandomNameService(resolve(Formatter)!, resolve(NAME_GETTER)!, resolve(NAME_URL)!),\n    'transient'\n   ),\n  provideFactory(Formatter, () => new FormatterUppercase()),\n    /* keep Value and Factory as they were*/\n]}>\n...\n<DependencyProvider\n```\n\n```jsx\n// SomeNameComponent.tsx\n<DependencyProvider provide={[provideFactory(Formatter, () => new FormatterLowercase())]}>...</DependencyProvider>\n```","readmeFilename":"README.md"}