Creating a MobX store from start to finish
Tags: mobx, javascript, react, store
Published: 2020-02-13 16:01:09

---
title: Creating a MobX store from start to finish
published: true
description: Creating a MobX store for beginners 
tags: [mobx, javascript, react, store]
---

[MobX](https://github.com/mobxjs/mobx) brings functional reactivity to JavaScript. It operates on 3 simple concepts:

- there is state
- state is modified by actions
- state is observed by reactions

Today we'll be using all 3 of them by creating what is know as a 'store'. It will store some state and modify it by providing actions.

Let's assume we are creating a Web App and we want to store client configuration data: Theme, language

### Setting up

We'll be using Typescript for some type-safety and handy decorators.


```ts
import { observable, configure } from 'mobx'

configure({ enforceActions: 'always' })

export type Theme = 'dark' | 'light'
export type Language = 'en' | 'fr'

export class ConfigStore {
	@observable language!: Language
	@observable theme!: Theme
}
```
We import the observable decorator from mobx to annotate our state as 'reactive' and we declare the `ConfigStore` with the appropriate state and types. We also configured MobX to enforce state changes to be done by actions only.

### Using defaults

It is always a good idea to have some defaults set. For the theme we will do a match media query, for the language we will check the navigator.

```ts
@observable language: Language = /en/i.test(window.navigator.language)
	? 'en'
	: 'fr'
@observable theme: Theme = window.matchMedia(`(prefers-color-scheme: dark)`)
    .matches
	? 'dark'
	: 'light'
```

### Creating actions

Action will be modifying our state, for that we import the `action` decorator and create the methods

```ts
import { action, observable, configure } from 'mobx'

// --snip--
	@action
	changeTheme = (theme: Theme) => {
		this.theme = theme
	}

	@action
	changeLanguage = (language: Language) => {
		this.language = language
	}
// --snip--
```

This might seem like boilerplate, but this ensures we always know where the state is modified. However you might find actions like 'setDark' or 'setFrench' more fitting to you.

### Reactions

Let's explore the power of reactions by setting up some caching system for our store. So far, each time the store is loaded (for example on a page refresh) we lose all of our state, this would mean a user would have to set his preferred theme on every page refresh!

Firstly we'll import `autorun` from mobx, it accepts a function and then runs it every time some observable in it was changed. This is great! 

```ts
import { autorun, action, observable, configure } from 'mobx'
```

Now back to our store, we will add 2 new methods, one for saving and one for loading the state. While saving is just deriving our state, loading is modifying it, therefore it has to be marked as an action.

```ts
	private save = () =>
		window.localStorage.setItem(
			ConfigStore.name,
			JSON.stringify({
				language: this.language,
				theme: this.theme
			})
		)
```

We mark it as private because it wont be used outside of the store. Every class/function has a static property `name`, in our case it is equal to `'ConfigStore'`, this will be the key to localStorage where we will be storing the cache. We handpick the state we wish to be saved, in this case language and theme. Then it is passed to a `JSON.stringify` to turn it into a string.

Loading is much simpler and wont have to be changed when you add new state properties:

```ts
	@action
	private load = () =>
		Object.assign(this, JSON.parse(window.localStorage.getItem(ConfigStore.name) || '{}'))
```

We retrieve the data from the cache, if it is empty we default to an empty object and we assign the result to `this`.

Let's now use the previously imported reaction

```ts
	constructor() {
		this.load()
		autorun(this.save)
	}
```

Yes, it is that simple, now your store is fully synced with the cache.

Lets inspect the lifespan of this store.

1. the store is initialized
2. the defaults are set
3. `this.load()` is called, it syncs the store with the cache
4. `this.save` is set to be auto-ran if any of the internal observables change
5. state is changed by an action
6. `this.save` is automatically ran because state has changed
7. cache is synced with the store!


Full store:

```ts
import { action, autorun, configure, observable } from 'mobx'

configure({ enforceActions: 'always' })

export class ConfigStore {
	@observable language: Language = /en/i.test(window.navigator.language) ? 'en' : 'fr'
	@observable theme!: Theme = window.matchMedia(`(prefers-color-scheme: dark)`).matches
		? 'dark'
		: 'light'

	constructor() {
		this.load()
		autorun(this.save)
	}

	private save = () =>
		window.localStorage.setItem(
			ConfigStore.name,
			JSON.stringify({
				language: this.language,
				theme: this.theme
			})
		)

	@action
	private load = () =>
		Object.assign(this, JSON.parse(window.localStorage.getItem(ConfigStore.name) || '{}'))

	@action
	changeLanguage = (language: Language) => {
		this.language = language
	}

	@action
	changeTheme = (theme: Theme) => {
		this.theme = theme
	}
}
```


## Extra: using in React

While this store is agnostic to the framework you'll be using, I'll show you how to use it with React. MobX has become one of the most popular choices as a state-management library for React (which despite its name is not reactive).

First, assume the store is in `stores/ConfigStore.ts`. Now create `stores/index.ts`:

```ts
import { createContext } from 'react'
import { ConfigStore } from './ConfigStore'

export const configStore = createContext(new ConfigStore())
```

This initializes a store and turns it into a context. 

React hooks have made working with MobX a boilerplate-free and type-safe experience. Lets use them:


`App.tsx`
```tsx
import { observer } from 'mobx-react-lite'
import { useContext } from 'react'
import { configStore } from 'stores'

const App = observer(() => {
	const config = useContext(configStore)

	return (
		<>
			<div style={{ backgroundColor: config.theme === 'dark' ? '#000000' : '#ffffff' }}>
				<label for="theme">Choose Theme:</label>

				<select id="theme" onChange={e => config.changeTheme(e.target.value)}>
					<option value="dark">Dark</option>
					<option value="light">Light</option>
				</select>
			</div>
		</>
	)
})

export default App
```