Create a useStore hook for mobx-state-tree
Tags: typescript, react
Published: 2021-07-19 20:53:56

## Mobx-state-tree?

From the mobx-state-tree docs:

> MobX is a state management "engine", and MobX-State-Tree gives it structure and common tools you need for your app.

This post will help you to create a `useStore` hook to use MST in functional components in a React project.

*Note: this post will be written in TypeScript.*

## Let's Get Hooked

First things first, let's create our "root store" (this will be our store that will hold our of other stores - more on that later)

```js
/// src/stores/rootStore.ts

import { types } from 'mobx-state-tree';

export const rootStore = types
  .model({})
  .create({});
```

### Explanation

From MST, we import the `types`. This allows us to create a "model", which will hold our data, as well as computed data, and actions to update our data.

## Context is Key

To use our hook in our React app, let's utilize React's Context API to help us do that.

```js
/// src/stores/rootStore.ts

// Add `Instance` to our import from MST
import { type, Instance } from 'mobx-state-tree';

const RootStoreContext = createContext<null | Instance<typeof rootStore>>(null);
export const StoreProvider = RootStoreContext.Provider;
```

```js
/// src/app.tsx

import { StoreProvider, rootStore } from './stores/rootStore';

export default function App(){
  return (
    <StoreProvider value={rootStore}>
      { /** rest of your app here */ }
    </StoreProvider>
  );
}
```

### Explanation

We will wrap our app with this `StoreProvider` and pass as its value, our `rootStore` from above.

## Now to create the hook

```js
/// src/stores/rootStore.ts

export function useStore(){
  const store = React.useContext(RootStoreContext);
  if(store === null){
    throw new Error('Store cannot be null, please add a context provider');
  }
  return store;
}
```

## Add some models

Now we can use this, but first, let us add a store into our `rootStore` so we can utilize this.

```js
/// src/stores/userStore.ts

import { types } from 'mobx-state-tree';

// example store, replace this with your actual stores
export const UserStore = types
  .model('UserStore')
  .props({
    id: types.identifier,
    name: types.string,
  })
  .actions((self) => ({
    setName: (name: string) => {
      self.name = name;
    }
}));

export const UserStoreInitialState = {
  id: '',
  name: '',
}
```

```js
/// src/stores/rootStore.ts

import { UserStore, UserStoreInitialState } from './userStore';

export const rootStore = types
  .model({
    userStore: UserStore,
  })
  .create({
    userStore: UserStoreInitialState,
  });
```

## Using our new hook

```js
/// src/components/user.ts

import { useStore } from '../stores/rootStore';

export function User(){
  const { userStore } = useStore();
  return (
    <div>
      <h1>{userStore.name}</h1>
      <button onPress={() => {
        userStore.setName('Colby');
      })>Update Name</button>
    </div>
  );
}
```

## What about re-rendering?

If you want your component to automatically re-render when state changes, use the `mobx-react-lite` package.

```js
/// src/components/user.ts

import { useStore } from '../stores/rootStore';
import { observer } from 'mobx-react-lite';

export function User observer((){
  const { userStore } = useStore();
  return (
    <div>
      <h1>{userStore.name}</h1>
      <button onPress={() => {
        userStore.setName('Colby');
      })>Update Name</button>
    </div>
  );
});
```

Wrapping any component that "observers" an MST model's state will automatically re-render when that state changes.

## All done!

And that's it! Mobx-state-tree (combined with mobx-react-lite for re-rendering) are amazing tools to keep in your tool belt, and a nice alternative to Redux (a lot less code to write to achieve what you want).

image credit: https://unsplash.com/photos/c9FQyqIECds