State Management With MobX: A Practical Walkthrough
State management libraries exist because tracking data as an application grows is hard. React and React Native developers have several options, including the Context API, Redux, MobX, and Unstated Next. MobX stands out for its simplicity and minimal boilerplate. It doesn't demand a specific architecture or drastic code changes. In many cases, removing all MobX decorators like @observable, @computed, @action, and observer leaves your code functionally identical.
MobX is not without trade-offs. It avoids imposing strict implementation rules, which can lead to inconsistent code. Debugging can also become challenging when state is modified directly in components instead of through actions.
Understanding MobX Fundamentals
MobX applies functional reactive programming transparently. The guiding principle: anything derivable from application state should be derived automatically. The library treats your application like a spreadsheet.
MobX rests on three core concepts:
- State: the data your application holds, essentially its memory contents.
- Derivations: anything derived from state without user interaction. There are two types:
- Computed values: values derived from current state using pure functions.
- Reactions: side effects from state changes, like console output, network requests, or updating the React component tree.
- Actions: code that modifies state, such as user events, backend data pushes, or scheduled events.
The golden rule: when creating a value based on current state, use a computed value.
An action example with an observable tick:
class Ticker {
@observable tick = 0
@action
increment() {
this.tick++ // 'this' will always be correct
}
}
const ticker = new Ticker()
setInterval(ticker.increment, 1000)
Here, an @observable tick starts at 0. The increment function, marked as an action, updates the value every second.
Observables and Stores
Observables can be JavaScript primitives, plain objects, classes, arrays, or maps. You declare them with @observable and assign values accordingly.
observable(value)
@observable classProperty = value
MobX's architectural pattern typically includes:
- Service: a function called from a container to fetch data from APIs and store it.
- Store: the central state holder containing observables, variables, actions, and computed properties.
- Container: calls the service, passes data from view models to view components as props, and should be marked with the
@observerdecorator.
Building a List App With MobX
To put these concepts into practice, we'll build a simple list application where users can add, view, and delete items. Mastery of basic JavaScript and React is assumed.
Project Setup
Initialize a project using create-react-app:
npx create-react-app listapp
Then move into the project directory:
cd listapp
The app requires three components:
TitleInputfor the project title and list input form.Listfor an individual list item with an add button.ListsDisplayto show all items and generate delete buttons.
A Store.js file will hold app state and modification methods, similar to Redux. Install these dependencies:
mobx: the state manager itself.mobx-react: official React bindings.bootstrapversion 4.5: for styling.uuid: for generating unique keys for list items.
Install them via yarn:
yarn add mobx mobx-react [email protected] uuid
Run the app in development mode:
yarn start
Creating the ListStore
Create a file in the root directory called ListStore, which serves as the central state location. This avoids repetition when the store is referenced across components. The store imports observable (for updatable variables), action (to modify state), and computed (for values derived from state changes).
/*** src/Store.js ***/
import { observable, action, computed } from "mobx";
import { v4 } from "uuid";
export class List {
@observable value
@observable done
constructor (value) {
this.id = v4()
this.value = value
}
}
export class ListStore {
@observable lists = []
@observable filter = ""
@action addList = (value) => {
this.lists.push(new List(value))
}
@action deleteList = (list) => {
this.lists = this.lists.filter(t => t !== list)
}
@computed get filteredLists () {
const matchCase = new RegExp(this.filter, "i")
return this.lists.filter(list=> !this.filter || matchCase.test(list.value))
}
}
The List class holds two properties: done and value, which represent the initial state and any modifications. UUID automatically generates keys so a delete button appears for each new list item.
The addList function uses the .push() method to add new lists to the @observable lists array. The deleteList function accepts the list item to remove, then resets this.Lists to a new array minus the selected item. Both functions are actions since they modify the app state.
Initializing the Store and Building Components
Import the store in App.js and initialize it so you can pass it as props to TitleInput and ListDisplay.
import React from 'react';
import Navbar from "./components/navbar";
import ListDisplay from "./components/ListDisplay";
import {ListStore} from './ListStore';
function App() {
const store = new ListStore()
return (
<div>
<Navbar store={store}/>
<ListDisplay store={store}/>
</div>
);
}
export default App;
Now build the ListDisplay component, which renders all added lists and automatically creates delete buttons.
import React from 'react'
import List from "./List";
import { observer } from 'mobx-react';
function ListDisplay(props) {
const { deleteList, filteredLists } = props.store
return (
<div>
<div className="container">
{filteredLists.map(list => (
<List key={list.id}
list={list}
deleteList={deleteList}
/>
))}
</div>
</div>
)
}
export default observer(ListDisplay)
This component is an observer that destructures list and deletelist from the store. It maps through filteredLists to build each individual list item, passing the returned item as props to the List component. The result displays lists with delete buttons for each.
Next, the List component exports the list as an observer so the store can watch for changes. Bootstrap provides card styling and aligns delete icons to the right. The delete button accepts object props and removes the selected item on click.
import React from 'react'
import { observer } from 'mobx-react'
function List(props) {
return (
<div className="card">
<div className="card-body">
<div className="d-flex justify-content-between
align-items-center">
<p className={`title ${props.list.done
? "text-secondary" : ""}`}>
{props.list.value}
</p>
<div>
<button
onClick={props.deleteList.bind(this, props.list)}
className="btn btn-danger
font-weight-bold py-2 px-5 ml-2">
Delete
</button>
</div>
</div>
</div>
</div>
)
}
export default observer(List)
The TitleInput component contains the input form for adding lists and the project title, completing the set of UI components that interact with the MobX store.
Wiring Up the Input Component
The TitleInput component follows the same pattern as before: it is wrapped in an @observer so it can react to changes coming from the MobX store. Inside the component, React Hooks manage a local values state initialized to an empty string, which holds whatever the user types into the field.
import React, { useState } from 'react'
import { observer } from 'mobx-react'
function Navbar(props) {
const [value, setValue] = useState("")
const {addList} = props.store
const prepareAddList = (e) => {
e.preventDefault()
addList(value)
setValue("")
}
return (
<div className="container mt-3">
<h1 className="title">List App</h1>
<form onSubmit={prepareAddList} className="form-group">
<div className="row ml-lg-2">
<input className="form-control-lg col-12 col-lg-9
col-sm-12 mr-3 border border-secondary"
value={value} type="text" onChange={(e) =>
setValue(e.target.value)} placeholder="Enter list"
/>
<button className="col-lg-2 col-5 col-sm-5 mt-2
mt-lg-0 mt-sm-2 btn btn-lg btn-success
font-weight-bold">
Add to List
</button>
</div>
</form>
</div>
)
}
export default observer(Navbar)
The component pulls the addList method from the store via props. A separate preparedAddList function wraps that method to handle the form’s onSubmit event and also takes care of the button’s click handler for manual submission.
With the component logic in place, restart the project server:
yarn start
The completed TitleInput should render as follows:
Assembling the App
All core components are ready. The next step is to bring everything together in App.js. This requires importing TitleInput and ListDisplay along with the store itself from the Store component.
MobX needs the store passed down as props, both to the top-level App and to each child component, so they can access the state and actions defined there.
import React from 'react';
import Navbar from "./components/navbar";
import ListDisplay from "./components/ListDisplay";
import {ListStore} from './ListStore';
function App() {
const store = new ListStore()
return (
<div>
<Navbar store={store}/>
<ListDisplay store={store}/>
</div>
);
}
export default App;
Once assembled, the final app should look like this:
Wrap-Up
MobX provides a pragmatic option for state management in React Native projects. Through this list-building example, we touched on its core ideas — state, derivations, and actions — and saw how they fit together in practice.
Using MobX in a future project is a reasonable next step to solidify these concepts and explore its capabilities further. A runnable demo of the app is available for reference.
Additional Reading
- React Native with MobX — Getting Started
- MobX Concepts & Principles
- Best Practices with React Hooks
- Building SSR Svelte Apps with SvelteKit
- Animating React Components with GreenSock
- An Introduction to Full Stack Composability




