Mastering State Management with MobX in React

Modern web applications are becoming increasingly dynamic, with data flowing through multiple components and user interactions happening in real-time. Managing that data flow efficiently is crucial — and while tools like Redux or Zustand dominate the ecosystem, MobX offers an elegant, reactive, and low-boilerplate alternative.

In this article, we’ll take a deep dive into MobX — exploring its core philosophy, how it works internally, how to use it effectively in React, and when it truly shines.

What is MobX?

MobX is a simple yet powerful state management library based on transparent functional reactive programming (TFRP). This means your UI automatically reacts to changes in your application state — without you having to write complex logic to keep them in sync.

At its core, MobX helps you:

  • Define observable data sources (state that can change)
  • Use actions to modify that data in predictable ways
  • Create computed values and reactions that update automatically

The beauty of MobX lies in how little code it takes to make your application reactive and maintainable.

How MobX Works Under the Hood

MobX uses a concept called observable graphs. When you mark something as observable (like a variable or object), MobX internally creates a dependency graph — connecting data with the parts of your code that use it.

Here’s the high-level flow:

  1. You mark a property as observable.
  2. You use that property inside a computed or reaction (like a React component).
  3. MobX automatically tracks these relationships.
  4. When the observable changes, MobX knows exactly which computations or components to update.

This dependency tracking allows MobX to update only what’s necessary, unlike Redux, where state changes often trigger wider re-renders.

Getting Started with MobX in React

Let’s set up a simple React + MobX project.

npm install mobx mobx-react-lite

Now, let’s create a store.

import { makeAutoObservable } from "mobx";

class TodoStore {
  todos = [];

  constructor() {
    makeAutoObservable(this);
  }

  addTodo(title) {
    this.todos.push({ title, completed: false });
  }

  toggleTodo(index) {
    this.todos[index].completed = !this.todos[index].completed;
  }

  get completedTodos() {
    return this.todos.filter(todo => todo.completed);
  }
}

const todoStore = new TodoStore();
export default todoStore;

Here’s what’s happening:

  • makeAutoObservable(this) automatically converts properties into observables, methods into actions, and getters into computed values.
  • completedTodos is a computed getter it’s automatically recalculated only when todos change.

Connecting MobX with React Components

Now, let’s use our store inside a React component.

import React from "react";
import { observer } from "mobx-react-lite";
import todoStore from "./TodoStore";

const TodoList = observer(() => {
  const { todos, addTodo, toggleTodo, completedTodos } = todoStore;

  return (
    <div>
      <h2>Todo List</h2>
      <input
        type="text"
        placeholder="New task"
        onKeyDown={(e) => {
          if (e.key === "Enter") addTodo(e.target.value);
        }}
      />
      <ul>
        {todos.map((todo, index) => (
          <li key={index} onClick={() => toggleTodo(index)}>
            {todo.completed ? <s>{todo.title}</s> : todo.title}
          </li>
        ))}
      </ul>
      <p>Completed: {completedTodos.length}</p>
    </div>
  );
});

export default TodoList;

The observer wrapper is the secret sauce. It tells React to re-render this component only when the observables it uses change.

There’s no need to manually trigger updates or dispatch actions. MobX handles it all behind the scenes.

Using Decorators (Advanced Syntax)

MobX also supports a decorator syntax, which provides a clean, declarative way to define your state.

npm install @babel/plugin-proposal-decorators

Then, in your store:

import { makeObservable, observable, action, computed } from "mobx";

class CounterStore {
  count = 0;

  constructor() {
    makeObservable(this, {
      count: observable,
      increment: action,
      decrement: action,
      double: computed,
    });
  }

  increment() {
    this.count++;
  }

  decrement() {
    this.count--;
  }

  get double() {
    return this.count * 2;
  }
}

export const counterStore = new CounterStore();

This pattern gives you more control over which properties are observable or computed and is especially helpful in larger codebases.

MobX Best Practices

As your project grows, keeping MobX code organized and efficient becomes important. Here are some best practices used in real-world applications:

1. Split Stores by Domain

Keep different parts of your state in separate stores (for example: UserStore, UIStore, ProductStore).
This separation keeps code modular and maintainable.

2. Avoid Over-Observing

Don’t make everything observable. Observe only data that truly affects the UI or other computed values.

3. Use Computed Values Wisely

Computed values are cached automatically — use them for derived state (like filtering, counting, or aggregating data).

4. Keep Actions Synchronous

While you can call async operations inside actions, it’s best to separate side effects (like API calls) into service layers.

5. Combine with Context API

You can easily share MobX stores using React’s Context API:

import React, { createContext, useContext } from "react";
import todoStore from "./TodoStore";

const StoreContext = createContext({ todoStore });
export const useStores = () => useContext(StoreContext);

export const StoreProvider = ({ children }) => (
  <StoreContext.Provider value={{ todoStore }}>
    {children}
  </StoreContext.Provider>
);

Then access it anywhere:

const { todoStore } = useStores();

MobX-State-Tree (MST): When You Need More Structure

For larger applications, MobX-State-Tree (MST) builds on top of MobX to provide:

  • Type-safe state models
  • Built-in snapshots and time-travel debugging
  • Middleware for actions
  • Better integration with TypeScript

Example:

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

const Todo = types.model({
  title: types.string,
  completed: false,
});

const TodoStore = types
  .model({
    todos: types.array(Todo),
  })
  .actions(self => ({
    addTodo(title) {
      self.todos.push({ title, completed: false });
    },
  }));

MST is ideal for enterprise-grade apps that need both reactivity and structure.

MobX vs Redux: A Real Comparison

FeatureMobXRedux
PhilosophyReactivePredictable & Immutable
BoilerplateVery lowHigh
Learning CurveEasyModerate
PerformanceExcellentGood
Debugging ToolsSimpleExtensive
Best ForReactive UI, real-time updatesLarge teams, predictable architecture

Redux is great for explicit state control and debugging. MobX excels when you need fast iteration, dynamic data, and minimal code overhead.

Common Pitfalls

  1. Overusing observables – Not every value needs to be tracked.
  2. Forgetting observer() – Without wrapping a component in observer, changes won’t trigger re-renders.
  3. Mutating state incorrectly – Always mutate through defined MobX actions.
  4. Memory leaks – Be careful with long-lived reactions or autoruns that aren’t disposed.

Final Thoughts

MobX proves that simplicity and power can coexist in state management.
Its reactive model feels natural — you write plain JavaScript, and the UI just responds.

If you’re building a complex app with many interdependent states, MobX can drastically reduce boilerplate, simplify logic, and improve performance.

While Redux remains a great choice for predictable and traceable state transitions, MobX shines in scenarios where developer productivity, reactivity, and clean code matter most.

Try MobX in your next project, and you might find yourself writing less code and shipping faster.

Share this content:

Hi, my name is Toni Naumoski, and I’m a Senior Frontend Developer with a passion for blending code and design. With years of experience as a Frontend Developer, Web Designer, and Creative Technologist, I specialize in crafting unique, responsive, and detail-oriented websites and web applications that stand out. I bring deep expertise in HTML, CSS, and JavaScript working fluently with modern frameworks like React, Angular, and Vue, as well as animation libraries like GSAP. My creative side thrives in Photoshop and Figma, and I enjoy extending functionality using tools like Express.js and ChatGPT. My work is guided by high integrity, strong communication, a positive attitude, and a commitment to being a reliable collaborator. I take pride in delivering high-quality digital experiences that are both technically solid and visually compelling.