inversify-react
---
Hooks and decorators for InversifyJS + React.
---
Table of Contents
+ [useInjection](#useinjection)
+ [useOptionalInjection](#useoptionalinjection)
+ [useContainer](#usecontainer)
+ [useAllInjections](#useallinjections)
+ [@resolve](#-resolve)
+ [@resolve.optional](#-resolveoptional)
Motivation
TL;DR:- InversifyJS, as IoC container, is great for automatic DI
- use it also in React
Installation
npm install --save inversify-react
yarn add inversify-react
- https://github.com/inversify/InversifyJS#installation
- https://github.com/loganfsmyth/babel-plugin-transform-decorators-legacy
- https://www.typescriptlang.org/docs/handbook/decorators.html
inversify-react
also uses decorators, but only when used in Class Components.
Usage overview
Usage is pretty similar to React Context.- Wrap React component tree with
Provider
andContainer
frominversify-react
– just like React Context.Provider
```js
import { Provider } from 'inversify-react';
...
<Provider container={myContainer}>
...
</Provider>
```
- Use dependencies from that container in child components
```ts
import { resolve, useInjection } from 'inversify-react';
...
// In functional component – via hooks
const ChildComponent: React.FC = () => {
const foo = useInjection(Foo);
...
};
// or in class component – via decorated fields
class ChildComponent extends React.Component {
@resolve
private readonly foo: Foo;
...
}
```
Provider
```js...
```
- provides contextual IoC container for children, similar to React Context.Provider
- can automatically establish hierarchy of containers in React tree when you use multiple Providers (e.g. in a big modular app)
- props:
* `container` - container instance or container factory function
* `standalone` - (optional prop, `false` by default) whether to skip [hierarchy of containers](https://github.com/inversify/InversifyJS/blob/master/wiki/hierarchical_di.md). Could be useful if you already control container hierarchy and would like to ignore React-tree-based hierarchy.
```ts
import as React from 'react';
import { Container } from 'inversify';
import { Provider } from 'inversify-react';
// in functional component
const AppOrModuleRoot: React.FC = () => {
return (
<Provider container={() => {
const container = new Container();
container.bind(Foo).toSelf();
container.bind(Bar).toSelf();
return container;
}}>
{/*...children...*/}
</Provider>
);
};
// or class component
class AppOrModuleRoot extends React.Component {
// you can create and store container instance explicitly,
// or use factory function like in functional component example above
private readonly container = new Container();
constructor(props: {}, context: {}) {
super(props, context);
const { container } = this;
container.bind(Foo).toSelf();
container.bind(Bar).toSelf();
}
render() {
return (
<Provider container={this.container}>
{/*...children...*/}
</Provider>
);
}
}
```
React hooks
useInjection
```ts const foo = useInjection(Foo); ```- very similar to React.useContext hook, resolves dependency by id
useOptionalInjection
```ts // e.g. Foo and Bar are not bound const foo = useOptionalInjection(Foo); // will return undefined // or const bar = useOptionalInjection(Bar, () => 'defaultBar'); // will return 'defaultBar' ```- resolves optional dependency
- default value can be defined via lazy resolving function (2nd argument)
```ts
const foo = useOptionalInjection(Foo, () => myDefault);
// foo === myDefault
// ^ Foo | typeof myDefault
```
That function conveniently receives container as argument, so you could instantiate your *default* using container (e.g. if it has dependencies)
```ts
const foo = useOptionalInjection(Foo, container => container.resolve(X));
```
useContainer
```ts const container = useContainer(); // or const foo = useContainer(container => container.resolve(Foo)); ```- low-level hook, resolves container itself
- has overload with callback to immediately resolve value from container, so could be used for more exotic API, e.g. named or tagged bindings
useAllInjections
```ts const bars = useAllInjections(Bar); ````- @see multi-inject
React component decorators (for classes)
@resolve
```ts @resolve foo: Foo; // or strict and semantic, see tips below @resolve private readonly foo!: Foo; ```- resolves service from container
- requires
reflect-metadata
andemitDecoratorMetadata
@resolve.optional
```ts @resolve.optional private readonly foo?: Foo; ```- tries to resolve service from container, but returns
undefined
if service cannot be obtained
- requires
reflect-metadata
andemitDecoratorMetadata
@resolve.optional(serviceId, defaultValue?)
- obtains service from container passed down in the React tree, returns
defaultValue
if service cannot be obtained
@resolve
private readonly foo!: Foo;
@resolve(Bar)
private readonly bar!: Bar;
@resolve.optional(Baz)
private readonly opt?: Baz;
...
}
// you can also use dependency in constructor,
// just don't forget to call super with context
// @see https://github.com/facebook/react/issues/13944
constructor(props: {}, context: {}) {
super(props, context);
console.log(this.foo.name);
}
```
@resolve.all
```ts @resolve.all('Foo') private readonly foo?: Foo; ```- tries to resolve all services from container, fails if no services are bound to given service identifier
- requires
reflect-metadata
andemitDecoratorMetadata
, but cannot be used without explicitly specifying service identifier
@resolve.all(serviceId)
- obtains services from container passed down in the React tree
@resolve.all(Baz)
private readonly opt?: Baz[];
...
}
```
@resolve.optional.all
```ts @resolve.optional.all('Foo') private readonly foo?: Foo; ```- tries to resolve all services from container, returns empty array if none are registered
- requires
reflect-metadata
andemitDecoratorMetadata
, but cannot be used without explicitly specifying service identifier
@resolve.optional.all(serviceId)
- obtains services from container passed down in the React tree
@resolve.optional.all(Baz)
private readonly opt?: Baz[];
...
}
```
Notes, tips
- \TypeScript tip\]
!
for@resolve
-ed fields is needed for strictPropertyInitialization / strict flags (which are highly recommended).
```ts
export interface IFoo {
// ...
}
export namespace IFoo {
export const $: interfaces.ServiceIdentifier<IFoo> = Symbol('IFoo');
}
```
```ts
container.bind(IFoo.$).to(...);
// ^ no need to specify generic type,
// type gets inferred from explicit service identifier
```
```ts
// in constructor injections (not in React Components, but in services/stores/etc)
constructor(@inject(IFoo.$) foo: IFoo)
// in React Class component
@resolve(IFoo.$)
private readonly foo!: IFoo; // less imports and less chance of mix-up
// in functional component
const foo = useInjection(IFoo.$); // inferred as IFoo
```