React dependency injection or similar? React dependency injection or similar? reactjs reactjs

React dependency injection or similar?


React has IoC, but not any concept of a DI container like Angular. That is, instead of having a container that knows how to create objects and passing in dependencies, you pass them explicitly by passing props to the component when you instantiate it (like <MyComponent items={this.state.items} />).

Passing dependencies as props isn't very common the React world though. Props are mostly used to pass data to components and not services/stores. But there's nothing stopping you from passing services/stores or even components as props (and certainly nothing wrong with it).

React has the concept of a context which is a shared object for a whole tree of components. So the top level component can say that the context for its subtree has an object containing something like a UserStore, a MessageStore, etc. A component further down in the component hierarchy can then say that it wants access to the UserStore in its context. By saying that, the UserStore is accessible to that component without having to explicitly pass it down from the top component to the bottom, and the component requesting it doesn't know how it was created/passed to it.

It has the benefit of a DI container where you have a central place for object creation which can be passed in further down. Here's a good intro to contexts: https://www.tildedave.com/2014/11/15/introduction-to-contexts-in-react-js.html

Contexts are still an undocumented feature of React, which means that its API can change in any coming versions of React, so you might want to use it sparsely until it becomes documented.


From react-in-patterns:

Most of the solutions for dependency injection in React components are based on context. I think that it's good to know what happens under the hood. As the time of this writing one of the most popular ways for building React apps involves Redux. The famous connect function and the Provider there use the context.

And from the react docs:

Context is an advanced and experimental feature. The API is likely to change in future releases.

Most applications will never need to use context. Especially if you are just getting started with React, you likely do not want to use context. Using context will make your code harder to understand because it makes the data flow less clear. It is similar to using global variables to pass state through your application.

If you have to use context, use it sparingly.

Regardless of whether you're building an application or a library, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.

I have found a way to inject dependencies without using the context thanks to the usage of an IoC container.

Most containers support two kinds of injections:

  • Constructor injection: In order to use “constructor injection” the IoC container needs to be able to create the instances of the classes. In React the components sometimes are just functions (not classes) and we can’t delegate the creation of the instances of the components to the IoC container. This means that constructor injection powered by IoC containers don’t play nicely with React.

  • Property injection: Works nicely with React if what we want is to pass dependencies to components without passing them explicitly through each component.

I use InversifyJS as IoC container and it property injection support to pass dependencies to components without passing them explicitly through each component and without using the context:

import { pInject } from "./utils/di";import { UserStore } from "./store/user";class User extends React.Component<any, any> {    @pInject(UserStore)    private userStore: UserStore; // INJECTED!    public render() {        return (            <h1>{this.userStore.pageTitle}</h1>        );    }}

The main advantage of using an IoC container like InversifyJS is that we are not using the context!

You can learn more about it here.


I don't really like using contexts, since it's still an experimental feature of react and kind of bulky. I've also looked at DI frameworks like react-di but it required each component to be aware of the DI framework's way of injecting dependencies (i.e. knowing that the dependency is in the this.props.di object.).

If we rule out contexts, the canonical way to inject something into a React component is through the use of props. The props are injected when you run React.createElement, i.e. for each jsx tag. The React.createElement function takes a component, some props and some children and returns a React element. I.e. (component, props, children) -> element.

I made a createComponent function with almost the same signature as React.createElement but which returns a component instead, i.e. (component, props, children) -> component. Here it is:

const createComponent = (type, defaultProps = {}, defaultChildren = null) => {    return ({ children, ...props }) => {        return React.createElement(            type,            { ...defaultProps, ...props },            children || defaultChildren        );    };};

The returned component can be injected in a prop, like in this example:

const Banner = ({ children, TextComponent }) => {    return <div className="banner">        <TextComponent>{children}</TextComponent>    </div>;}const SayHelloComponent = ({ ParagraphComponent }) => {    return <ParagraphComponent>Hello world!</ParagraphComponent>;}const ParentComponent = () => {    const inject = {        ParagraphComponent: createComponent(Banner, {            TextComponent: createComponent('span', {                className: "my-pretty-class",            }),        }),    }    return <SayHelloComponent {...inject} />;}

Fiddle: https://jsfiddle.net/8971g8s5/3/

The good thing about this is that PropTypes will still work very well, so each component can clearly declare what kind of properties it wants.

Also, the receiving end of the injection doesn't need to depend on any special implementation, just the normal props system of React. So the components needn't know that you're using dependency injection or how you do it, they just care about what props they receive.