DEV Community

loading...
Cover image for Create a useStore hook for mobx-state-tree

Create a useStore hook for mobx-state-tree

Colby Garland
Software Engineer, passionate about TypeScript, Flutter and Mobx-state-tree.
・2 min read

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({});
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode
/// src/app.tsx

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

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

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;
}
Enter fullscreen mode Exit fullscreen mode

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

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

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

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
});
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)