Integrating Preact with MobX State Tree

I’ve had a whale of a time trying to integrate Preact with MobX state tree lately. I finally succeeded, and here’s how.

tl;dr: here’s the example repo.

Prerequisites

We’ll need to get ourselves MST, MobX itself, Preact of course, and a glue library - mobx-preact. All told, my package.json dependencies section looks like this:

"dependencies": {
    "mobx": "^6.9.0",
    "mobx-preact": "^3.0.0",
    "mobx-state-tree": "^5.1.8",
    "preact": "^10.13.2"
},

Setting up the store

Then, we’ll need to set up a few things. We can begin with our store itself:

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

const RootStore = types.model("RootStore", {
  // ...
}).actions(self => {
  // ...
})

export const store = RootStore.create({ ... });

It’s not very important what goes into the store itself, here, so I’m just skipping over these sections. So far, it’s as you’d expect - bog-standard MST root store.

Time for some magic.

Creating a store provider

import { createContext } from "preact";

const RootStoreContext = createContext();
export const StoreProvider = ({ children }) => (
  <RootStoreContext.Provider value={store}>{children}</RootStoreContext.Provider>
)

We use createContext to build a blank context. With that, we build a context1 which will eventually have our store in it. Contexts are a way to pass information deep into our app without having to pass it via props to every single component.

Creating a store “hook”

It’s not really a hook. It’s just a function that starts with use.

import { useContext } from "preact/compat"; // preact/compat, not preact-compat!

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

This references our context so we can fish out the value of store wherever we choose in our application.

Wrapping our app with the store provider

const App = () => (
  <StoreProvider>
    <h1>Preact + MobX State Tree example</h1>
    <Todos />
  </StoreProvider>
)

render(<App />, document.getElementById('app'))

Here’s the important bit. We import our StoreProvider. Everything that’s beneath it - in this case, our entire application - will have access to the context, and consequently be able to useStore.

Using the store

import {observer} from "mobx-preact";

export const TodoList = observer(() => {
  const { todos, deleteTodo } = useStore();

  return (
    <ul>
      {todos.map((todo, ix) => <Todo todo={todo} deleteTodo={() => deleteTodo(ix)} />)}
    </ul>
  )
})

The only catch we need to look out for is the observer() function wrapping our component. I’m told these can also be used as decorators, but from a cursory reading it looks like it’s for class components only? I don’t really know. I’m not a frontend person 😅

In any case, we can useStore here to access bits and pieces of our store - properly destructured, of course.

In action

You can find the entire thing on my GitHub at paweljw/preact-mst-todo.


  1. Preact contexts work pretty much the same as React’s version. More information can be had at https://react.dev/learn/passing-data-deeply-with-context.↩︎