Beginners guide to mobx-state-tree in 5 minutes or less
Tags: mobx, javascript, beginners, mobxstatetree
Published: 2021-02-02 15:20:59

## The problem we're trying to solve

Let's jump right in!  We have a problem, we want to show our customers a simple image viewer.  

We'll show a simple slideshow:
```
┌───────────────────────────────────────┐
│                                       │
│                                       │
│                Image 1                │
│                                       │
│                                       │
└───────────────────────────────────────┘
┌───────────┐ ┌───────────┐ ┌───────────┐
│           │ │           │ │           │
│  Image 1  │ │  Image 2  │ │  Image 3  │
│(selected) │ │           │ │           │
│           │ │           │ │           │
└───────────┘ └───────────┘ └───────────┘
```

## The data model

In Mobx-State-Tree (MST) you're working with models.  What is a model?

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

const Slide = types.model("Slide", {
  id: types.identifier,
  url: types.string,
  description: types.string,
  selected: types.optional(types.boolean, false),
});
```

This `Slide` model gives us a blueprint for an observable slide.  Here's an example of hydrating that model with data:

``` javascript
const slide = Slide.create({
  id: "1",
  url: "http://url_to_the_image/whatever.jpg",
  description: "Grey cat",
});
```

Cool beans!  We have a slide.

Here's your new slide serialized:

``` javascript
slide.toJSON()
{
  id: "1",
  url: "http://url_to_the_image/whatever.jpg",
  description: "Grey cat",
  selected: false,  // cool, it defaulted to false
}
```

Now what?  Well, not much.  Models in MST are only editable via actions.  What are actions you ask?  Here's an example:

``` javascript
const Slide = types
  .model("Slide", {
    id: types.identifier,
    url: types.string,
    description: types.string,
    selected: types.optional(types.boolean, false),
  })
  .actions((self) => ({
    setSelected: (isSelected) => {
      self.selected = isSelected;
    },
  }));
```

Let's use that new action:

``` javascript
slide.selected // false
slide.setSelected(true) // calling the action
slide.selected // true
```

Now we're able to modify our slide.  Great!  Much like a tree falling in the woods, does a modified slide change anything if no one is listening?  I'll let you ponder that one while we add an observer.  What's an observer you ask?  Great question! 

An observer is something that listens to changes within an observable.  They're used to trigger side effects.  Like updating your UI or printing something to the console. 

If you were reading carefully above, you'll remember when I mentioned: "This `Slide` model gives us a blueprint for an **observable** slide."  If we're creating observables, it goes to reason that we can observe them.  MST is built on mobx.  Mobx makes observing changes easy.  Observe :-)

``` javascript
import { autorun } from "mobx";

autorun(() => {
    console.log('Slide is selected: ' + slide.selected)
})
// Slide is selected: false 
slide.setSelected(true);
// Slide is selected: true 
```

[`autorun`](https://mobx.js.org/reactions.html#autorun) is a simple observer that will watch any observable that is used within it.  It's also run once determine what it needs to watch.

There are many ways to observe observables via [reactions](https://mobx.js.org/reactions.html).

If you're using React, there are already [tools](https://mobx.js.org/react-integration.html) available to easily observe your models -- most notably, mobx-react-lite's observer() function. I'll show you an example of how that works near the end of this article.

Now you know how to create models, hydrate them with data, change their state and react to changes!

From here, we need to add another model that represents the collection of slides.

## Collecting the slides into a slideshow
We have a slide, that's cool... But it's not enough.  We need to turn that one slide into a slideshow.  Here's a start:

``` javascript
const SlideShow = types.model("SlideShow", {
  slides: types.array(Slide),
});
```

This is still not enough.  We could show a slideshow at this point, but we couldn't interact with it.  In addition, we have to do a little digging to find the selected slide.  Let's first take care of finding the selected slide.

``` javascript
const SlideShow = types
  .model("SlideShow", {
    slides: types.array(Slide),
  })
  .views((self) => ({
    get selectedSlide() {
      return self.slides.find((slide) => slide.selected);
    },
  }));
```
`selectedSlide` is a view.  That view is observable just like any other field.  One of the major tenets of mobx is that "Anything that can be derived from the application state, should be. Automatically."  Views are how this is done.

Let's work on being able to select a slide.  In order to do that, two things must happen.  First, the currently selected slide should be de-selected. Second, the slide to select should be set as such.

There are a few ways to go about selecting a slide.  We could call upon the parent SlideShow to toggle the selected states.  The api would probably look something like this:
``` javascript
slideShow.setSelectedSlide("2") // pass the slide id to select
// OR
slideShow.setSelectedSlide(slideShow.slides[2]) // pass the slide
```

The bummer for me in this option is that you have to keep track of both the SlideShow and the slide wherever you want to trigger a selection.  Chances are you'll have the slide handy that you'd like to select when it's clicked for example.

I'd prefer an api that looks more like this:

``` javascript
slide.select()
```

So, let's build that!

``` javascript
import { types, getParent } from "mobx-state-tree";

const Slide = types
  .model("Slide", {
    id: types.identifier,
    url: types.string,
    description: types.string,
    selected: types.optional(types.boolean, false),
  })
  .actions((self) => ({
      setSelected: (isSelected) => {
          self.selected = isSelected
      },
    select: () => {
      getParent(self, 2).selectedSlide.setSelected(false);
      self.setSelected(true);
    },
  }));

const SlideShow = types
  .model("SlideShow", {
    slides: types.array(Slide),
  })
  .views((self) => ({
    get selectedSlide() {
      return self.slides.find((slide) => slide.selected);
    },
  }));

const slideShow = SlideShow.create({
  slides: [
    {
      id: "1",
      url: "http://url_to_the_image/grey.jpg",
      description: "Grey cat",
      selected: true,
    },
    {
      id: "2",
      url: "http://url_to_the_image/blue.jpg",
      description: "Blue cat",
    },
    {
      id: "3",
      url: "http://url_to_the_image/yellow.jpg",
      description: "Yellow cat",
    },
  ],
});

slideShow.selectedSlide.description; // Grey cat
slideShow.slides[2].select();
slideShow.selectedSlide.description; // Yellow cat
```

And with that, we have a working, observable slideshow model!  Not much of a UI...  Let's fix that now.

## Adding a UI

So that model is pretty terrific...  But it's a little hard for most people to use right now.  It's time to create a derivation of our data in the form of a UI.  

Why did I call our UI a "derivation of our data"?  Because it is :-)! The data model acts as the source of truth about the state of our app.  The UI is just one of many potential derivations of that data.  Analytics, debugging, native apps...  Everyone wants a piece of the action. 

Let's look at one very simple React based UI:

{% codesandbox simple-mobx-state-tree-slideshow-j3hrs %}

Here, I'm using `observer`s from mobx-react to watch for changes in my data model.  The observers are automatically optimized to only update when an observed piece of data changes.  Not so important with this trivial example.  But as applications grow, it becomes more important.

Well, that's all for now.  Next time, I think we'll look at how to test our data model.

Until then, have fun out there!  I know I am!

-Ruby