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).
image credit: https://unsplash.com/photos/c9FQyqIECds
Top comments (1)
Just a heads up that
mobx-store-provider
can do this for free.