Create a useStore hook for mobx-state-tree

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)

/// 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.

/// 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;
/// 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

/// 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.

/// 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: '',
}
/// src/stores/rootStore.ts

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

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

Using our new hook

/// 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.

/// 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).

26