Using MobX in React requires connecting MobX's reactive state with React's rendering mechanism. MobX provides multiple ways to achieve this integration.
Installing Dependencies
bashnpm install mobx mobx-react-lite # or npm install mobx mobx-react
Usage Methods
1. Using observer HOC (mobx-react)
javascriptimport React from 'react'; import { observer } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); // Wrap component with observer const TodoList = observer(() => { return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ); }); export default TodoList;
2. Using useObserver Hook (mobx-react-lite)
javascriptimport React from 'react'; import { useObserver } from 'mobx-react-lite'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); function TodoList() { return useObserver(() => ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> )); } export default TodoList;
3. Using useLocalObservable Hook (mobx-react-lite)
javascriptimport React from 'react'; import { observer, useLocalObservable } from 'mobx-react-lite'; function TodoList() { const store = useLocalObservable(() => ({ todos: [], addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); }, toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } })); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> ); } export default observer(TodoList);
4. Using React Context to Share Store
javascriptimport React, { createContext, useContext } from 'react'; import { observer } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const TodoContext = createContext(null); function TodoProvider({ children }) { const store = new TodoStore(); return ( <TodoContext.Provider value={store}> {children} </TodoContext.Provider> ); } function useTodoStore() { const store = useContext(TodoContext); if (!store) { throw new Error('useTodoStore must be used within TodoProvider'); } return store; } const TodoList = observer(() => { const store = useTodoStore(); return ( <div> <h1>Todo List</h1> <ul> {store.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => store.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => store.addTodo('New Todo')}> Add Todo </button> </div> ); }); function App() { return ( <TodoProvider> <TodoList /> </TodoProvider> ); } export default App;
5. Using Provider and inject (mobx-react legacy)
javascriptimport React from 'react'; import { Provider, observer, inject } from 'mobx-react'; import { makeAutoObservable } from 'mobx'; class TodoStore { todos = []; constructor() { makeAutoObservable(this); } addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } toggleTodo(id) { const todo = this.todos.find(t => t.id === id); if (todo) todo.completed = !todo.completed; } } const todoStore = new TodoStore(); @inject('todoStore') @observer class TodoList extends React.Component { render() { const { todoStore } = this.props; return ( <div> <h1>Todo List</h1> <ul> {todoStore.todos.map(todo => ( <li key={todo.id}> <input type="checkbox" checked={todo.completed} onChange={() => todoStore.toggleTodo(todo.id)} /> <span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}> {todo.text} </span> </li> ))} </ul> <button onClick={() => todoStore.addTodo('New Todo')}> Add Todo </button> </div> ); } } function App() { return ( <Provider todoStore={todoStore}> <TodoList /> </Provider> ); } export default App;
Best Practices
1. Wrap components that need reactive updates with observer
javascript// ✅ Correct: only wrap components that need reactive updates const TodoItem = observer(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li> )); const TodoList = ({ store }) => ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ); // ❌ Wrong: wrap entire app, may cause unnecessary renders const App = observer(() => ( <div> <Header /> <TodoList /> <Footer /> </div> ));
2. Reasonably split Store
javascript// ✅ Correct: split Store by function class TodoStore { @observable todos = []; @observable filter = 'all'; @action addTodo(text) { this.todos.push({ id: Date.now(), text, completed: false }); } } class UserStore { @observable user = null; @action setUser(user) { this.user = user; } } class AppStore { todoStore = new TodoStore(); userStore = new UserStore(); } // Share using Context const StoreContext = createContext(new AppStore());
3. Use computed to optimize performance
javascriptclass TodoStore { @observable todos = []; @observable filter = 'all'; @computed get filteredTodos() { switch (this.filter) { case 'completed': return this.todos.filter(todo => todo.completed); case 'active': return this.todos.filter(todo => !todo.completed); default: return this.todos; } } } const TodoList = observer(({ store }) => ( <ul> {store.filteredTodos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ));
4. Use React.memo to optimize child components
javascriptconst TodoItem = observer(React.memo(({ todo }) => ( <li> <input type="checkbox" checked={todo.completed} onChange={() => todo.toggle()} /> <span>{todo.text}</span> </li> )));
5. Use useEffect to cleanup side effects
javascriptimport { useEffect } from 'react'; import { reaction } from 'mobx'; const TodoList = observer(({ store }) => { useEffect(() => { const disposer = reaction( () => store.todos.length, (length) => { console.log('Todo count changed:', length); } ); return () => disposer(); }, [store]); return ( <ul> {store.todos.map(todo => ( <TodoItem key={todo.id} todo={todo} /> ))} </ul> ); });
Common Issues
1. Component not updating
javascript// ❌ Wrong: forgot to use observer function TodoList({ store }) { return ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ); } // ✅ Correct: use observer const TodoList = observer(({ store }) => ( <ul> {store.todos.map(todo => ( <li key={todo.id}>{todo.text}</li> ))} </ul> ));
2. Modifying state outside observer component
javascript// ❌ Wrong: directly modify state outside observer component function App() { const store = useStore(); useEffect(() => { store.todos.push({ id: 1, text: 'Todo' }); // Not in action }, []); return <TodoList store={store} />; } // ✅ Correct: modify state in action function App() { const store = useStore(); useEffect(() => { store.addTodo('Todo'); // In action }, []); return <TodoList store={store} />; }
3. Overusing observer
javascript// ❌ Wrong: overusing observer const Header = observer(() => <header>Header</header>); const Footer = observer(() => <footer>Footer</footer>); const Main = observer(() => <main>Main</main>); // ✅ Correct: only use observer on components that need reactive updates const Header = () => <header>Header</header>; const Footer = () => <footer>Footer</footer>; const Main = observer(() => <main>Main</main>);
Summary
- Use observer or useObserver to connect components to MobX
- Use React Context to share Store
- Reasonably split Store, organize by function modules
- Use computed to optimize performance
- Use React.memo to optimize child components
- Remember to cleanup reactions in useEffect
- Avoid overusing observer
- Always modify state in actions