26
Create a useStore hook for 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.
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({});
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.
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>
);
}
We will wrap our app with this StoreProvider
and pass as its value, our rootStore
from above.
/// 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;
}
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,
});
/// 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>
);
}
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.
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
26