How to: mobx-state-tree + react + typescript
Tags: mobxstatetree, typescript, react
Published: 2019-04-06 21:20:45

---
title: How to: mobx-state-tree + react + typescript
published: true
description: A guide on how to design and implement domain stores in mobx-state-tree and connect them to react components, all in typescript
tags: mobx-state-tree, typescript, react
---

This is a walkthrough on how to get a full setup with `mobx-state-tree` and `react` in a `CRA` app with `typescript`. This guide doesn't focus too much on the theory or how things work under the hood and mostly includes practical examples (code!) on *how to* make things work.

I have been mostly using `redux` in all of my work and side projects, and eventually got curios about the other side of the state management world with `mobx` and decided to jump right into `mobx-state-tree`.

Trying to make `mobx-state-tree` work in `react` with `typescript` appeared to be quite a struggle. Especially making everything properly typed (no cheating with `any`!) in `Typescript` was a challenge, so when eventually everything fell in place I thought I would share my setup in order to (hopefully) make someone else's life easier :)

The application I build is a simple poll maker that allows to create a new poll, publish it, view and delete published polls. The source code with a cute little demo is available on my [github](https://github.com/MargaretKrutikova/mst-react-ts-guide).

Here are the quick links to jump to directly if you have a particular problem that is covered:

- [Setup stores in mobx-state-tree](#setup-stores-in-mobx-state-tree)
  - [Create a base model](#create-a-base-model)
  - [Use composition to create domain stores](#use-composition-to-create-domain-stores)
  - [CRUD on models in a nested list](#crud-on-models-in-a-nested-list)
  - [Convert between models](#convert-between-models)
  - [Root store](#root-store)
  - [Communicate between stores](#communicate-between-stores)
- [Connect react to mobx](#connect-react-to-mobx)
  - [Why not mobx-react](#why-not-mobx-react)
  - [mobx-react-lite to the rescue](#mobx-react-lite-to-the-rescue)
  - [Context provider to pass store](#context-provider-to-pass-store)
  - [Custom hook to inject stores](#custom-hook-to-inject-stores)

## Setup stores in mobx-state-tree

I started developing my app with designing stores of the domain area in `mobx-state-tree` and was immediately faced with the following "how-to"s:

- how to create a base model and use composition to extend it with properties and functionality in different stores,
- how to create a store with a nested list of items representing another model and perform CRUD operations on it,
- how to create a root store composing all the other domain stores,
- how to communicate between stores.

I figured those might be common problems when designing stores for any domain area, so I will go through them in more detail and show my solutions.

In my poll-maker app there is going to be a base model `PollBase`, a store responsible for creating a new poll `PollDraft`, a model for a published poll `PublishedPoll` and a store for published polls `PublishedPolls`.

### Create a base model

Before we start, install the necessary dependencies:

```shell
yarn add mobx mobx-state-tree
```

Now let's create a base model for the domain object `poll`, which will have a poll question and a list of choices, and abase model for choice with a string property and id:

```typescript
import { types } from "mobx-state-tree"

const PollChoiceBase = types.model("PollChoiceBase", {
  id: types.identifier,
  value: types.optional(types.string, "")
})

const PollBase = types.model("PollBase", {
  question: "",
  choices: types.optional(types.array(PollChoiceBase), [])
})
```

### Use composition to create domain stores

A poll that is being edited (let's call it a draft poll) and not yet published will have the same properties as `PollBase`, but also actions to edit those properties. Similar, a choice of a draft poll will have the same shape as `PollChoiceBase` with an action to update it:

```typescript
const PollDraftChoice = PollChoiceBase.actions(self => ({
  setChoice(choice: string) {
    self.value = choice
  }))

const PollDraft = types
  .compose(PollBase,
    types.model({
      choices: types.optional(types.array(PollDraftChoice), [])
    })
  )
  .actions(self => ({
    setQuestion(question: string) {
      self.question = question
    }
}))
```

A published poll can no longer be edited, so it won't have editing actions but it needs an extra property `id` to be able to find it or create an external link to it:

```typescript
const PublishedPoll = types.compose(
  PollBase,
  types.model({
    id: types.identifier
  })
)
```

### CRUD on models in a nested list

A draft poll has a list of choices, that can be added, edited and removed. Currently we have an action to update a choice (`setChoice`), but no actions to remove an existing choice or add a new one.

Here adding is rather trivial, but removal is a bit tricky. We want to be able to use `choice.remove()` somewhere in a `react` component, but actions can only modify the model they belong to or their children, so a choice can't simply remove itself and can only be removed by its parent `PollDraft` since it "owns" the list of choices. This means `PollDraftChoice` model will need a `remove` action which will delegate its removal to `PollDraft`, which we can retrieve via `getParent` helper from `mobx-state-tree`.

Here is the code (I use [shortid](https://github.com/dylang/shortid) to generate unique ids):

```typescript
import { destroy, getParent, Instance, cast } from "mobx-state-tree"

// Instance is a typescript helper that extracts the type of the model instance
type PollDraftChoiceModel = Instance<typeof PollDraftChoice>
type PollDraftModel = Instance<typeof PollDraft>

const PollDraftChoice = PollChoiceBase.actions(self => ({
  ...
  remove() {
    const pollDraftParent = getParent<PollDraftModel>(self, 2)
    pollDraftParent.removeChoice(cast(self))
  }
}))

const PollDraft = types.compose(...)
  .actions(self => ({
    ...
    addChoice(choice: string) {
      self.choices.push({ id: shortid(), value: choice })
    },
    removeChoice(choiceToRemove: PollDraftChoiceModel) {
      destroy(choiceToRemove)
    }
}))

```

Here is what is happening inside `PollDraftChoice`:

- `getParent<PollDraftModel>(self, 2)` means fetch parent 2 levels up - one until you reach `items` property and one more until you reach `PollDraft` itself, and assume that the returned parent is of type `PollDraftModel`.
- `pollDraftParent.removeChoice(cast(self))` uses [`cast`](https://github.com/mobxjs/mobx-state-tree/blob/master/docs/API/README.md#cast) helper to tell typescript that `self` is indeed of type `PollDraftChoiceModel`. Why is it necessary? The problem is that `self` here is of type what was [before](https://github.com/mobxjs/mobx-state-tree#typing-self-in-actions-and-views) views and actions are applied, which means at that point `self` is actually not of type `PollDraftChoiceModel`, so `pollDraftParent.removeChoice(self)` won't compile in TS.

### Convert between models

Let's create our second domain store to keep track of published polls:

```typescript
import { types, Instance, getSnapshot } from "mobx-state-tree"

type PublishedPollModel = Instance<typeof PublishedPoll>
type PollDraftModel = Instance<typeof PollDraft>

export const PublishedPolls = types
  .model({
    polls: types.optional(types.array(PublishedPoll), [])
  })
  .actions(self => ({
    publishDraft(pollDraft: SnapshotIn<PollDraftModel>) {
      const pollToPublish = { ...pollDraft, id: shortid() }
      self.polls.push(pollToPublish)
    }
  }))
```

Here `publishDraft` takes in a `snapshot` of a poll draft. [Snapshot](https://github.com/mobxjs/mobx-state-tree#snapshots) in `mobx-state-tree` is a plain object stripped from all type information and actions and can be automatically converted to models. 

So why does `publishDraft` need to take in a snapshot and not just `PollDraftModel`? That's because an instance of `PollDraftModel` can't be converted to a published poll since it will have extra actions that aren't compatible with `PublishedPollModel`, and will cause a runtime exception. So, by specifying `SnapshotIn<PollDraftModel>` we explicitly say that we want the raw data that exists on `PollDraftModel`.

Next problem is that `publishDraft` action has to be called somewhere from outside, either from the `PollDraft` store or from some kind of `RootStore`. Let's see how we can make that happen and establish some communication between the two stores.

### Root store

Let's create a root store to combine all stores used in the app: `PollDraft` and `PublishedPolls`:

```typescript
type RootStoreModel = Instance<typeof RootStore>

const RootStore = types.model("RootStore", {
  pollDraft: PollDraft,
  publishedPolls: PublishedPolls
})
```

### Communicate between stores

One way of communicating between stores, is to use `getRoot` from `mobx-state-tree` to fetch the root store and from there get the necessary store, or use `getParent` to traverse the tree. This works fine for tightly coupled stores (like `PollDraft` and `PollDraftChoice`), but won't scale if used in more decoupled stores.

One way to enable store communication is to make use of `getEnv` function that can inject environment specific data when creating a state tree (from [mobx-state-tree docs](https://github.com/mobxjs/mobx-state-tree#dependency-injection)). So we can just inject a newly created store into the whole state tree. One caveat here is that the environment can't be passed directly into one of the child stores and needs to be passed into the root store, otherwise you get this error:

```shell
Error: [mobx-state-tree] A state tree cannot be made part of another state tree 
as long as their environments are different.
```

Let's create a function called `createStore`, similar to `redux`'s `configureStore`, that would create all individual stores, create the environment and assemble them all together in one root store. The environment will have only one property of `PublishedPolls` store since it needs to be accessed from `PollDraft` when publishing a poll draft:

```typescript
type RootStoreEnv = {
  publishedPolls: PublishedPollsModel
}

const createStore = (): RootStoreModel => {
  const publishedPolls = PublishedPolls.create()
  const pollDraft = PollDraft.create()

  const env: RootStoreEnv = { publishedPolls }

  return RootStore.create({ pollDraft, publishedPolls }, env)
}
```

Now, `PolLDraft` store can define a `publish` action and call `publishDraft` on `publishedPolls`:

```typescript
import { types, getEnv, getSnapshot } from "mobx-state-tree"

const PollDraft = types
  .compose(...)
  .actions(self => ({
    ...
    publish() {
      const snapshot = getSnapshot(self)

      const env = getEnv<RootStoreEnv>(self)
      env.publishedPolls.publishDraft(snapshot)
    }
  }))

```

### Connect to redux devtools

We will use `connectReduxDevtools` middleware from the package `mst-middlewares` that will connect the state tree to the redux devtools (more info and configuration options available in the [docs](https://github.com/mobxjs/mobx-state-tree/tree/master/packages/mst-middlewares#connectreduxdevtools)). In order to setup the connection we will use a monitoring tool [`remotedev`](https://github.com/zalmoxisus/remotedev). Install the packages first:

```shell
yarn add --dev remotedev mst-middlewares
```

and add the following code after the store creation:

```typescript
import { createStore } from "../stores/createStore"
import { connectReduxDevtools } from "mst-middlewares"

const rootStore = createStore()

connectReduxDevtools(require("remotedev"), rootStore)
```

## Connect react to mobx

The part I struggled most with is how to connect `react` to `mobx` and start using stores in my components. The idea here is that react components need to become "reactive" and start tracking observables from the stores.

### Why NOT mobx-react

The most common way to achieve this is by using [mobx-react](https://github.com/mobxjs/mobx-react) which provides `observer` and `inject` functions, where `observer` is wrapped around components to make them react to changes and re-render and `inject` just injects stores into components. However, I wouldn't recommend using this library because:

- when using `observer`, the component loses the ability to use hooks because it gets converted to a class, more on this [here](https://github.com/mobxjs/mobx-react/issues/594). And the docs recommend in the [best practices](https://mobx.js.org/best/pitfalls.html) to use `observer` around as many components as possible, which means hooks can't be used almost anywhere,
- `inject` function is quite compilcated and doesn't work well with typescript (see [github issue](https://github.com/mobxjs/mobx-react/issues/256#issuecomment-433587230)), requiring all stores to be marked as optional and then using `!` to indicate that they actually exist.

### mobx-react-lite to the rescue

Luckily there is another library, [`mobx-react-lite`](https://github.com/mobxjs/mobx-react-lite), which is built with hooks and provides `observer` wrapper. One thing worth mentioning, `observer` doesn't support classes, but there is a dedicated component `Observer` that can be wrapped around parts of `jsx` in render in class components.

It is easy to get confused with this library since it provides a lot of hooks like `useObservable`, `useComputed` etc. that are going to be deprecated according to [the docs](https://github.com/mobxjs/mobx-react-lite#notice-of-deprecation). Instead here is a [recommended way](https://github.com/mobxjs/mobx-react/issues/256#issuecomment-433587230), that we are going to follow:

- use `react context` provider to pass down the store(s),
- access the store using `useContext` hook with a selector, alternatively inject the necessary stores with a custom `useInject` hook based on the `useContext` hook,
- wrap components with `observer` from `mobx-react-lite` to subscribe to changes.

So let's install the library:

```shell
yarn add mobx-react-lite
```

### Context provider to pass store

First, let's create context `StoreContext`, that will later receive the root store as its `value`, and export provider and a custom hook for accessing the context value:

```typescript
const StoreContext = createContext<RootStoreModel>({} as RootStoreModel)

export const useStore = () => useContext(StoreContext)
export const StoreProvider = StoreContext.Provider
```

And then create the root store with `createStore` and send it into `StoreProvider` which we wrap around `App`:

```jsx
import { StoreProvider } from "./StoreProvider"
import { createStore } from "../stores/createStore"

const rootStore = createStore()

const Root: React.FunctionComponent<{}> = () => (
  <StoreProvider value={rootStore}>
    <App />
  </StoreProvider>
)
```

### Custom hook to inject stores

It is possible to use the `useStore` hook directly to access the root store and get the necessary data from it, like this:

```typescript
const { pollDraft } = useStore()
```

I also implemented a `useInject` hook that takes in a mapping function and returns a mapped object, similar to how it is done in `redux` with `mapStateToProps`. This hook is somewhat close to the idea of [custom inject](https://github.com/mobxjs/mobx-react#customizing-inject) with a mapper function, but with hooks. So if you have a more complicated app with lots of things in your store, you might want to get only the things you want and not care about the rest.


In its simplest form, `useInject` hook might look like this:

```jsx
export type MapStore<T> = (store: RootStoreModel) => T

const useInject = <T>(mapStore: MapStore<T>) => {
  const store = useStore()
  return mapStore(store)
}
```

The `PollDraft` component would then use `useInject` to access `pollDraft` store from the root store:

```jsx
import { observer } from "mobx-react-lite"
import { RootStoreModel } from "../stores/RootStore"
import useInject from "../hooks/useInject"

const mapStore = (rootStore: RootStoreModel) => ({ pollDraft: rootStore.pollDraft })

const PollDraft: React.FunctionComponent<{}> = observer(() => {
  const { pollDraft } = useInject(mapStore)

  return (
    <div>
      <h1>Create a new poll</h1>
      <input
        value={pollDraft.question}
        onChange={e => pollDraft.setQuestion(e.target.value)}
      />
      <button onClick={pollDraft.publish}>Publish</button>
    </div>
  )
})
```

This is especially useful if `mapStore` function is more complicated and involves combining data and actions from several stores.

---

At this point I felt like I covered the basics and created a setup that I could continue building upon or use it as a boilerplate for projects with a similar stack. The source code can be found on my [github](https://github.com/MargaretKrutikova/mst-react-ts-guide). 

I hope this walkthrough was useful and you found something that helped you in your projects. Would love to hear your feedback on what you think was helpful or share your own experience with `mobx-state-tree` and `react` in `typescript`!