React is a popular JavaScript library for building user interfaces. It's often used in conjunction with a state management library to handle the application state. One such library is MobX, which makes state management simple and efficient. MobX stands out for its simplicity and ease of use, especially when compared to other state managers like Redux.
MobX is a battle-tested library that uses functional reactive programming principles to automatically detect changes in your application state and update the user interface accordingly. This makes it a powerful tool for building complex applications with minimal boilerplate code.
One of the key features of MobX is the MobX state tree, also known as MST. This is a state container that combines the simplicity and ease of use of MobX with the structure and formality of Redux. It allows you to manage the state of your application in a systematic and predictable manner, making it easier to reason about and debug your program.
Before we dive into how to use MobX with React, it's important to understand some of the core concepts of MobX. These include observable states, computed values, and actions.
Observable state refers to the data in your application that can change over time. In MobX, any piece of data can be made observable, including simple data types like numbers and strings, as well as complex data types like objects and arrays.
Computed values are values that are derived from the observable state. They are automatically updated whenever the state they depend on changes. This makes it easy to create complex data relationships in your application.
Actions are functions that modify the observable state. They are the only way to change the state in a MobX application, ensuring that all state changes are predictable and easy to trace.
To get started with MobX in your React application, you first need to install the necessary packages. This includes the core MobX library, as well as the MobX React bindings package, which allows you to use MobX in your React components.
You can install these packages using npm or yarn. Here's how you can do it:
1 npm install mobx mobx-react 2
Once you've installed the packages, you can import them into your React components like this:
1 import React from 'react'; 2 import { observer } from 'mobx-react'; 3
The observer function is a higher-order component that allows your React components to react to changes in the observable state. We'll talk more about this in a later section.
A MobX store is where you keep your observable state. It's essentially an object that contains your application state and the actions that modify it.
Here's a simple example of a MobX store:
1 import { observable, action } from 'mobx'; 2 3 class TodoStore { 4 @observable todos = []; 5 6 @action addTodo(todo) { 7 this.todos.push(todo); 8 } 9 } 10 11 export default new TodoStore(); 12
In this example, we have a TodoStore class with an observable todos array and an addTodo action that adds a new todo to the array. The @observable and @action decorators are used to mark the todos array and the addTodo function as observable and action, respectively.
Observables are one of the key concepts in MobX. They represent the reactive state of your application, i.e., the state that can change over time and that your components can react to.
In MobX, any piece of data can be made observable. This includes simple data types like numbers and strings, as well as complex data types like objects and arrays. When a piece of data is made observable, MobX automatically tracks any changes to it and updates any computed values or reactions that depend on it.
Here's an example of how you can create an observable value in MobX:
1 import { observable } from 'mobx'; 2 3 const store = observable({ 4 count: 0, 5 increment() { 6 this.count++; 7 } 8 }); 9
In this example, we're creating an observable object with a count property and an increment method. The count property is an observable value that can change over time, and the increment method is an action that changes the count value.
When you change the count value by calling the increment method, MobX automatically detects the change and updates any computed values or reactions that depend on the count value. This is the essence of reactive programming in MobX.
Computed values are another important concept in MobX. They are values that are derived from the observable state and are automatically updated whenever the state they depend on changes.
Computed values are defined using the computed function in MobX. Here's an example:
1 import { observable, computed } from 'mobx'; 2 3 class TodoStore { 4 @observable todos = []; 5 6 @computed get completedTodosCount() { 7 return this.todos.filter(todo => todo.completed).length; 8 } 9 } 10
In this example, completedTodosCount is a computed value that returns the number of completed todos. It's automatically updated whenever the todos array changes.
Actions are functions that modify the observable state. They are the only way to change the state in a MobX application, ensuring that all state changes are predictable and easy to trace.
Actions are defined using the action function in MobX. Here's an example:
1 import { observable, action } from 'mobx'; 2 3 class TodoStore { 4 @observable todos = []; 5 6 @action addTodo(todo) { 7 this.todos.push(todo); 8 } 9 } 10
In this example, addTodo is an action that adds a new todo to the todos array. When you call this action, MobX automatically detects the change to the todos array and updates any computed values or reactions that depend on it.
MobX React Lite is a separate React bindings package for MobX that provides a lighter and more modern API for using MobX with React. It's based on React's new Hooks API and provides a simpler and more intuitive way to use MobX in your React components.
Here's an example of how you can use MobX React Lite to create a reactive component:
1 import React from 'react'; 2 import { useLocalObservable, Observer } from 'mobx-react-lite'; 3 4 function App() { 5 const store = useLocalObservable(() => ({ 6 count: 0, 7 increment() { 8 this.count++; 9 } 10 })); 11 12 return ( 13 <Observer> 14 {() => ( 15 <div> 16 Count: {store.count} 17 <button onClick={store.increment}>Increment</button> 18 </div> 19 )} 20 </Observer> 21 ); 22 } 23 24 export default App; 25
In this example, we're using the useLocalObservable hook to create a local observable store inside the App component. The Observer component is used to render a part of the component that reacts to changes in the observable state.
The observer function is a higher-order component provided by MobX React that makes a React component reactive. It allows the component to react to changes in the observable state and re-render whenever the state changes.
Here's an example of how you can use the observer function in a React component:
1 import React from 'react'; 2 import { observer } from 'mobx-react'; 3 4 const TodoList = observer(({ store }) => ( 5 <ul> 6 {store.todos.map(todo => ( 7 <li key={todo.id}>{todo.text}</li> 8 ))} 9 </ul> 10 )); 11 12 export default TodoList; 13
In this example, the TodoList component is wrapped with the observer function, making it reactive. Whenever the todos array in the store changes, the TodoList component is automatically re-rendered to reflect the new state.
Now that we've covered the basics of MobX and React, let's put everything together and build a simple Todo app. This app will have a list of todos and a form to add new todos. We'll use MobX for state management and React for rendering the user interface.
First, let's create our MobX store:
1 import { observable, action } from 'mobx'; 2 3 class TodoStore { 4 @observable todos = []; 5 6 @action addTodo(text) { 7 const todo = { 8 id: Date.now(), 9 text, 10 completed: false 11 }; 12 13 this.todos.push(todo); 14 } 15 } 16 17 export default new TodoStore(); 18
Next, let's create our TodoList component:
1 import React from 'react'; 2 import { observer } from 'mobx-react'; 3 4 const TodoList = observer(({ store }) => ( 5 <ul> 6 {store.todos.map(todo => ( 7 <li key={todo.id}>{todo.text}</li> 8 ))} 9 </ul> 10 )); 11 12 export default TodoList; 13
Finally, let's create our TodoForm component:
1import React, { useState } from "react"; 2 3const TodoForm = ({ store }) => { 4 const [text, setText] = useState(""); 5 6 const handleSubmit = (e) => { 7 e.preventDefault(); 8 store.addTodo(text); 9 setText(""); 10 }; 11 12 return ( 13 <form onSubmit={handleSubmit}> 14 <input 15 type="text" 16 value={text} 17 onChange={(e) => setText(e.target.value)} 18 /> 19 <button type="submit">Add Todo</button> 20 </form> 21 ); 22}; 23 24export default TodoForm; 25
And there you have it - a simple Todo app built with MobX and React!
While MobX is great for managing simple state, it can get a bit tricky when you have complex state that involves nested objects and arrays. This is where MobX State Tree (MST) comes in.
MST is an opinionated state management library built on top of MobX. It provides a way to define your application state as a tree of observable objects, with built-in support for actions, computed values, and more.
Here's an example of how you can define a MobX State Tree:
1 import { types } from 'mobx-state-tree'; 2 3 const Todo = types.model({ 4 id: types.identifier, 5 text: types.string, 6 completed: types.boolean 7 }); 8 9 const TodoStore = types.model({ 10 todos: types.array(Todo), 11 }).actions(self => ({ 12 addTodo(text) { 13 const todo = { 14 id: Date.now(), 15 text, 16 completed: false 17 }; 18 19 self.todos.push(todo); 20 } 21 })); 22 23 export default TodoStore.create({ todos: [] }); 24
In this example, we're defining a Todo model with id, text, and completed properties, and a TodoStore model with a todos array and an addTodo action. The TodoStore.create method is used to create an instance of the TodoStore model.
One of the main benefits of using MobX over other state managers is its simplicity and ease of use. MobX uses a straightforward and intuitive API that makes it easy to understand and use, even for beginners.
Another benefit of MobX is its performance. MobX uses a fine-grained reactivity system that only updates the parts of your application that need to be updated, resulting in efficient and fast updates.
MobX also provides a lot of flexibility in how you structure your application state. You can use plain objects, classes, or MobX State Tree, depending on your needs and personal preference.
Finally, MobX has a strong and active community that provides a wealth of resources and support, including a comprehensive official documentation, tutorials, and examples.
Functional reactive programming (FRP) is a programming paradigm that deals with data that changes over time and the propagation of change. It's the underlying concept behind MobX and what makes it so powerful.
In FRP, the state of your application is represented as a graph of dependencies between observable values, computed values, and reactions. When an observable value changes, MobX automatically propagates the change to all computed values and reactions that depend on it, ensuring that your application state is always consistent and up-to-date.
Here's a simple example of how FRP works in MobX:
1 import { observable, computed, autorun } from 'mobx'; 2 3 const store = observable({ 4 count: 0, 5 increment() { 6 this.count++; 7 } 8 }); 9 10 const doubleCount = computed(() => store.count * 2); 11 12 autorun(() => { 13 console.log(`Count: ${store.count}, Double Count: ${doubleCount.get()}`); 14 }); 15 16 store.increment(); // Logs: "Count: 1, Double Count: 2" 17 store.increment(); // Logs: "Count: 2, Double Count: 4" 18
In this example, store.count is an observable value, doubleCount is a computed value that depends on store.count, and the autorun function creates a reaction that logs the store.count and doubleCount values whenever they change. When you call store.increment, MobX automatically updates the doubleCount value and the autorun reaction.
In a React application, user events like clicks and form submissions are typically handled by event handler functions that update the application state. In a MobX application, these event handler functions are often actions that modify the observable state.
Here's an example of how you can handle a button click event in a MobX and React application:
1 import React from 'react'; 2 import { observer } from 'mobx-react'; 3 4 const Counter = observer(({ store }) => ( 5 <div> 6 Count: {store.count} 7 <button onClick={store.increment}>Increment</button> 8 </div> 9 )); 10 11 export default Counter; 12
In this example, the Counter component renders a count value and a button. When the button is clicked, the store.increment action is called, which updates the store.count value. Since the Counter component is wrapped with the observer function, it automatically re-renders whenever the store.count value changes.
In a real-world application, you often need to make network requests to fetch or update data from a backend server. In a MobX application, you can handle network requests in actions that update the observable state based on the response from the server.
Here's an example of how you can fetch data from a server in a MobX application:
1 import { observable, action } from 'mobx'; 2 import axios from 'axios'; 3 4 class TodoStore { 5 @observable todos = []; 6 @observable isLoading = false; 7 8 @action fetchTodos() { 9 this.isLoading = true; 10 axios.get('/api/todos') 11 .then(response => { 12 this.todos = response.data; 13 this.isLoading = false; 14 }); 15 } 16 } 17 18 export default new TodoStore(); 19
In this example, the fetchTodos action makes a GET request to the /api/todos endpoint to fetch a list of todos. When the request is made, the isLoading value is set to true. When the response is received, the todos array is updated with the data from the response, and the isLoading value is set back to false.
MobX is not just for web applications - you can also use it with React Native to build mobile applications. The API and concepts of MobX are the same in React Native as they are in React, so you can apply everything you've learned in this post to your React Native applications.
Here's an example of how you can use MobX in a React Native application:
1 import React from 'react'; 2 import { observer } from 'mobx-react'; 3 import { Button, Text, View } from 'react-native'; 4 5 const Counter = observer(({ store }) => ( 6 <View> 7 <Text>Count: {store.count}</Text> 8 <Button title="Increment" onPress={store.increment} /> 9 </View> 10 )); 11 12 export default Counter; 13
In this example, the Counter component is a React Native component that renders a count value and a button. When the button is pressed, the store.increment action is called, which updates the store.count value. Since the Counter component is wrapped with the observer function, it automatically re-renders whenever the store.count value changes.
MobX is not tied to any specific UI framework - you can use it with any framework that can react to changes in your application state. However, MobX is most commonly used with React due to its reactive nature and the strong integration provided by the MobX React bindings.
The role of MobX in a UI framework like React is to manage the application state and automatically update the UI whenever the state changes. This allows you to write components that are purely a function of the state, making your code easier to understand and reason about.
While MobX is relatively easy to use, there are a few common pitfalls that you might encounter, especially if you're new to MobX or reactive programming in general.
One common pitfall is mutating the observable state outside of actions. In MobX, all state mutations should be done in actions to ensure that all state changes are predictable and easy to trace. If you mutate the state outside of actions, you might end up with inconsistent state and hard-to-debug issues.
Another common pitfall is not using the observer function to make your React components reactive. If you forget to wrap your components with the observer function, they won't react to changes in the observable state, and you might wonder why your UI is not updating as expected.
Finally, when using MobX State Tree, a common pitfall is mutating the state tree directly instead of using actions. In MST, all state mutations should be done in actions to ensure that the state tree remains immutable and that all state changes are recorded for debugging and time-travel debugging.
MobX and Redux are both popular state management libraries for React, but they have different philosophies and use cases.
Redux is based on the principles of Flux and functional programming, and it uses a single immutable state tree and pure reducer functions to manage the application state. This makes Redux predictable and easy to debug, but it can also be verbose and complex, especially for beginners or for simple applications.
On the other hand, MobX is based on the principles of reactive programming and it uses observable state and automatic dependency tracking to manage the application state. This makes MobX simple and intuitive, but it can also be less predictable and harder to debug, especially for complex applications or for developers who are not familiar with reactive programming.
So, which one is better? It really depends on your needs and personal preference. If you prefer simplicity and intuition, or if you're building a simple application or a prototype, MobX might be a better choice. If you prefer predictability and control, or if you're building a complex application or a large-scale project, Redux might be a better choice.
In conclusion, MobX is a powerful and flexible state management library for React that makes it easy to manage your application state and automatically update your UI whenever the state changes. It's simple and intuitive, yet powerful and efficient, making it a great choice for both simple and complex applications.
However, like any tool, MobX is not a one-size-fits-all solution. Whether it's the right choice for your React application depends on your needs, your personal preference, and the specific requirements of your project.
Moreover, to speed up your React app development, try using DhiWise React Builder and take your app to the market faster with the courtesy of its intelligent UI builder.
So experiment, create amazing React apps with robust features, and keep building apps that provide users with efficient, enjoyable, and highly satisfying functionalities. Happy coding!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.