Dependency Injection in React with Jpex
Tags: react, typescript, testing, ioc
Published: 2020-12-17 11:56:11

---
title: Dependency Injection in React with Jpex
published: true
description: 
tags: react, typescript, testing, ioc
//cover_image: https://direct_url_to_image.jpg
---

Dealing with side effects in React is a tricky subject. I'm sure we've all started by writing something like this:
```tsx
const Users = () => {
  const [ users, setUsers ] = useState();

  useEffect(() => {
    window.fetch('/api/users').then(res => res.json()).then(data => setUsers(data));
  }, []);

  if (users == null) {
    return null;
  }

  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};
```

But this is pretty dirty. You're fetching from an api inline, managing app state inilne, etc.

## Just use hooks!
When we're talking about dealing with side effects and state in components, the common solution is simply **use hooks to abstract**:

```tsx
const useUsers = () => {
  const [ users, setUsers ] = useState();

  useEffect(() => {
    window.fetch('/api/users').then(res => res.json()).then(data => setUsers(data));
  }, []);

  return users;
};

const Users = () => {
  const users = useUsers();

  if (users == null) {
    return null;
  }

  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};
```
That's better right? Now the component is much simpler. However, both the beauty and the problem with hooks is that they're just regular functions, this is doing literally the exact same thing as the first example. You're still fetching data from an api.

How would you write a unit test for this hook, or the component? You'd probably mock the fetch function by overriding `window.fetch` right?

```ts
spyOn(window, 'fetch').mockImplementation(fakeFetchFn);

renderHook(useUsers);

expect(window.fetch).calledWith(...);

window.fetch.mockRestore();
```

This is really dirty if you ask me. You're having to stub a global property, attempt to revert it after the test, hope that nothing bleeds between tests. You could also use something like [msw](https://www.npmjs.com/package/msw) to intercept the actual api requests? This has the same problem. If you've ever tried to use a concurrent test runner (like ava or jest's concurrent mode), you'll quickly encounter issues with this sort of thing.

> It's important to make the distinction of unit testing vs integration testing. Integration tests encourage you test how your code works with other code. You'd want to stub as little as possible in order to get a more realistic setup. But for unit testing, you should be soley focused on a single unit of code without the distraction of external side effects.

To complicate our example further let's say we also need to use a cookie in our request:
```ts
const useUsers = () => {
  const [ users, setUsers ] = useState();
  const jwt = cookies.get('jwt');

  useEffect(() => {
    window.fetch('/api/users', {
      headers: {
        authorization: jwt,
      }
    }).then(res => res.json()).then(data => setUsers(data));
  }, []);

  return users;
};
```

## Invert control
The ideal solution would be to _invert the control_ of your code. Imagine if we had complete control of what the hook _thinks_ are its dependencies?

```tsx
const useUsers = (window: Window, cookies: Cookies) => {
  const [ users, setUsers ] = useState();
  const jwt = cookies.get('jwt');

  useEffect(() => {
    window.fetch('/api/users', {
      headers: {
        authorization: jwt,
      }
    }).then(res => res.json()).then(data => setUsers(data));
  }, []);

  return users;
};

const Users = () => {
  const users = useUsers(window, cookies);

  if (users == null) {
    return null;
  }

  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};
```

So now we can actually safely test our hook:

```tsx
renderHook(() => useUsers(fakeWindow, fakeCookies));

expect(fakeWindow.fetch).calledWith(...);
```

Great! Now we have completely isolated that component's dependencies. But do you really want to be passing these things in every time? And how would you write a unit test for your component? Pass window/cookies in as props? Gross. We still don't have a large scale solution to this problem.

After this extremely long introduction, here's my solution:

## Jpex

[Jpex](https://www.npmjs.com/package/jpex) is a lightweight dependency injection container powered by typescript. It works with "vanilla" typescript but really shines when used with react. Unlike something like [inversify](https://www.npmjs.com/package/inversify) it's not limited to OOP classes with experimental decorators, you can inject _anything_, _anywhere_!

So let's rewrite the example using jpex. First we want to register our `cookies` dependency:

```ts
import jpex from 'jpex';
import cookies, { Cookies } from 'my-cookies-library';

jpex.constant<Cookies>(cookies);
```
This tells jpex that whenever it sees the `Cookies` type it is talking about the `cookies` variable.

We don't need to register the `Window` as jpex understands that it's a global object and can inject it automatically.

Now we can rewrite our react hook:

```ts
import { encase } from 'react-jpex';

const useUsers = encase((window: Window, cookies: Cookies) => () => {
  const [ users, setUsers ] = useState();
  const jwt = cookies.get('jwt');

  useEffect(() => {
    window.fetch('/api/users', {
      headers: {
        authorization: jwt,
      }
    }).then(res => res.json()).then(data => setUsers(data));
  }, []);

  return users;
});
```
Well that's almost the same right? `encase` tells jpex "when somebody calls this function, resolve and inject its parameters, and return the inner function". The awesome thing about jpex is it is able to infer the dependencies purely based on their types. You could call window `fuzzything` and as long as it has a type of `Window` jpex understands.

Let's see our component:
```tsx
const Users = () => {
  const users = useUsers();

  if (users == null) {
    return null;
  }

  return (
    <ul>
      {users.map(user => (
        <li>{user.name}</li>
      ))}
    </ul>
  );
};
```
No change there! The component can just call the hook like a regular function. It doesn't need to understand or provide the hook's dependencies, yet we now have control of them.

Let's write a test for the hook now:

```tsx
import { Provider } from 'react-jpex';

const wrapper = ({ children }) => (
  <Provider onMount={jpex => {
    jpex.constant<Cookies>(fakeCookies);
    jpex.constant<Window>(fakewindow);
  }}>
    {children}
  </Provider>
);

renderHook(useUsers, { wrapper });

expect(fakeWindow.fetch).calledWith(...);
```
So what is happening here? The `Provider` component creates a new _instance_ of jpex completely sandboxed for this test. We then pass an `onMount` prop that registers our stubbed dependencies. When our hook is called, it receives the stubbed dependencies.

Now lets consider how you could test a component that uses our hook:
```tsx
import { Provider } from 'react-jpex';

const wrapper = ({ children }) => (
  <Provider onMount={jpex => {
    jpex.constant<Cookies>(fakeCookies);
    jpex.constant<Window>(fakewindow);
  }}>
    {children}
  </Provider>
);

render(<Users/>, { wrapper });

await screen.findByText('Bob Smith');
```

Yup, it's the same! We have completely inverted control of our application so we can inject dependencies in from any level!

This is only the tip of the jpex iceberg. It's proven invaluable for things like storybook, hot-swapping dependencies based on environment, and abstracting our infrastructure layer. And although I've mostly focused on React usage, jpex is agnostic. You can use it with anything, and it works on the browser and in node!

Check it out! https://www.npmjs.com/package/jpex