Dependency Inversion in React

Using React Context

Vittorio Guerriero
4 min readApr 19, 2019
Photo by Jenna Hamra from Pexels

Dependency inversion is satisfied by using interfaces in dependencies.

interface CoffeeMachine {
expresso(): void;
}

function CoffeeShop(coffeeMachine: CoffeeMachine) {
return {
expresso: () => {
coffeeMachine.expresso();
}
}
}

In this example we simply create a CoffeeShop that can make an espresso, the coffee machine implementation can be changed with another implementation and the CoffeeShop would not know the difference.

When I started developing in React I’ve found a lot of example online using concrete implementation directly inside a component.

import axios from "axios";
import * as React from "React";
function PostComponent() {
const [post, setPost] = React.useState(null);

axios.get(`url`).then(res => {
setPost(res.data);
})

return post && <div>
{post.title}
</div>
}

This component is depending on axios. How do we refactor this code to follow the dependency inversion principle?

What if we just wrap axios in a http function?

import axios from "axios";function Http() {
return {
get: async<T>(url: string): Promise<T> => {
return axios.get(url).then(data => data.data);
}
}
}

And now the component will import http.

import { http } from './http';function PostComponent() {http().get("url").then(...
...
}

As you can see PostComponent is still depending indirectly on axios. So you don’t really depend on an abstraction.

What we really want is to make sure PostComponent does not depend on axios directly (see the image below).

  • Post Component depends on IHttp
  • Http implements IHttp and depends on axios
  • Post Component does not depend on either axios or http

We could create a wrapper of axios and pass it in the parameters of the component as interface

Yes. Let’s do it.

1 — Create the interface IHttp

http.interface.ts

interface IHttp {
get<T>(url: string): Promise<T>;
}

2 — Http Implement the interface

http.ts

import axios from "axios";function Http(): IHttp {
return {
get: async<T>(url: string): Promise<T> => {
return axios.get(url).then(data => data.data);
}
}
}

3 — Pass IHttp to the component

import { APP_CONTEXT } from './context';
import { Http } from './http';
import { IHttp } from './http.interface';
import { appDependencies } from './dependencies';
const http: IHttp = Http();
export function App(): JSX.Element {
return (
<PostComponent http={http}/>
);
}

As you can see PostComponent does not depend on axios! Yuppieeee

import { IHttp } from './http.interface';function PostComponent({http: IHttp}) {
...
}

This is perfect when your application is really small.

What if there are another 4–5 components between App and PostComponent? Do we move the dependency to the closest component? Why should the closest component know about http?

Ideally we want to create the http implementation at the app level because in the future it will require more configuration and we want to create it once. We don’t want to spread this implementation around the app.

So, what we do?

We could use React.Context!

In react we have the Context API that can be used as a Service Locator

1 — Create your context as null and define the interface

context.ts

import { IHttp } from './http.interface';export const APP_CONTEXT: React.Context<AppContext> = React.createContext(null);

export interface AppContext {
http: IHttp;
}

2 — Create your dependencies initialisation

dependencies.ts

import { IHttp } from './http.interface';
import { Http } from './http';
const http: IHttp = Http();export const appDependencies: AppContext = {
http: http
}

3 — Add the context to your app

import * as React from "React";
import { APP_CONTEXT } from './context';
import { appDependencies } from './dependencies'
export function App(): JSX.Element {
return (
<APP_CONTEXT.Provider value={appDependencies}>
<PostComponent/>
</APP_CONTEXT.Provider>
);
}

4 — Use it in PostComponent

import * as React from "React";
import { APP_CONTEXT } from './context';
function PostComponent() {
const http: IHttp = React.useContext(APP_CONTEXT).http;
const [post, setPost] = React.useState(null);

http.get(`url`).then(res => {
setPost(res.data);
})

return post && <div>
{post.title}
</div>
}

Done.

  • PostComponent depends on AppContext, so on IHttp
  • AppDependencies implements the AppContext and depends on Http, so on Axios

What is the benefit of creating and developing your application with this approach? React context allows you to take advantage of dependency inversion. You can provide a different implementation of the same interface

export function App() {
return (
<POST_CONTEXT.Provider value={dependenciesA}>
<PostComponent/>
</POST_CONTEXT.Provider/>

<POST_CONTEXT.Provider value={dependenciesB}>
<PostComponent/>
</POST_CONTEXT.Provider/>

<POST_CONTEXT.Provider value={dependenciesC}>
<PostComponent/>
</POST_CONTEXT.Provider/>
);
}

Conclusions

Benefits

  1. This solution will respect dependency inversion principle
  2. Unit test will be easier
  3. Components are not coupled to core libraries
  4. Switching dependencies is efficient

Disadvantages

  1. The creation of dependencies has to be done manually, you don’t have IOC (Inversion of Control)

Thanks to Giulio that helped me understand the differences between IOC, DI (Dependency Injection) and DIP (Dependency Inversion Principle)

--

--