What problem do we need to solve?
We all want some way to manage state in an application. Nowadays, almost all developers can easily to build a Web Application with Components. For example in React, React Components have their states then we can define and manage these states by using useState() hooks.
import { useState } from "react"; const Button = () => { const [toggle, setToggle] = useState<boolean>(false); return ( <div> <input type="button" value="Click me" onClick={() => setToggle((prev) => !prev)} /> {toggle && <p>Hello World!</p>} </div> );
}; export default Button;
Look it's so easy, right? However, let's picture that you are creating a large website with many components, each having numerous states. And sometimes one component needs to share its state with another components or modify state of another component. So now you need a powerful library like MobX, Redux or even a combination of useReducer and useContext for managing your app's state.
Now, let's dive into MobX concepts. Curious why I choosing MobX over Redux? Checkout the title of this topic, please 🌝.
What is MobX
Mobx is a powerful and flexible state management library commonly used in React applications. It is designed to simplify the management of state and make it more predictable by using reactive programming principle.
😵💫😵💫😵💫 Reactive Programmning? I'm not sure what that is either, but I'll research and write it in the future blog 🤓🤓🤓
How to integrate MobX with React for state management
Install Mobx 🛠️
In this tutorial, I assumed that you have already known how to create a React Project.
So you can follow this page to add MobX to your project.
You can choose either mobx-react-lite
or mobx-react
, the choice is yours. But in this tutorial I prefer to use mobx-react-lite
.
Let's start coding now 🥹
Imagine you have an order page that includes your selected items (OrderItem) and the delivery milestones.
So, our state has two main attributes: the list of selected items
and the list of delivery milestones
.
And now for use MobX for managing our OrderPage, I break it down into three simple steps below:
Firstly, define a state class that use will be shared for all components in the order page
import { makeAutoObservable } from "mobx";
import { DeliVeryStatus, OrderItem } from "model/Order"; class OrderState { items: OrderItem[] = []; deliveryStatus: DeliVeryStatus | undefined = undefined; constructor() { makeAutoObservable(this); }
} export default OrderState;
Additionally, I have provided the definition for the model OrderItem and the data type of DeliveryStatus here:
export interface Product { id: string; name: string; price: number;
} export type DeliVeryStatus = "Confirmed" | "Preparing Goods" | "Delivering" | "Delivered" | "Canceled" | "Return Goods"; export interface OrderItem { product: Product; amount: number;
}
Secondly, create a Provider to supply the context of OrderState to all child components within the OrderPage
import OrderState from "mobx/order";
import { createContext, useContext } from "react"; /** * Interface Definition * This defines the types for the properties that the OrderPageProvider component will accept. */
interface Props { children: React.ReactElement; state: OrderState;
} // Create a context to share the OrderState props, yes it's can be undefined
const Context = createContext<OrderState | undefined>(undefined); /** * This is a functional component that acts as a context provider * @param props Props * @returns It returns a Context.Provider component that provides the state (an instance of OrderState) to its children. */
const OrderPageProvider: React.FC<Props> = (props: Props) => { return <Context.Provider value={props.state}>{props.children}</Context.Provider>;
}; export default OrderPageProvider; /** * This is a custom hook that makes it easier to access the OrderState from the context. * @returns It casts the value of the context to OrderState and returns it */
export const useOrderState = () => { const state = useContext(Context); return state as OrderState;
};
Finally, let's create the order page now
import OrderPageProvider, { useOrderState } from "context/OrderContext";
import { observer } from "mobx-react-lite";
import OrderState from "mobx/order";
import { useState } from "react"; // Don't forget to wrap this function by observer(), or your state won't update when there are any state changes 😁😁😁
const ListItems = observer(() => { const state = useOrderState(); return ( <div> <ul> {state.items.map((item) => { return ( <li> <div>{item.product.name}</div> <div>{item.amount}</div> <input type="button" value="increase" onClick={() => { //Handle increasing item }} /> <input type="button" value="decrease" onClick={() => { //Handle decreasing item }} /> <input type="button" value="delete" onClick={() => { //Handle deleting item }} /> </li> ); })} </ul> </div> );
}); // I'm a bit lazy, so you can use a similar mindset to the ListItems component above for coding 😅😅😅
// Don't forget to wrap this function by observer(), or your state won't update when there are any state changes 😁😁😁
const DeliveryMilestone = observer(() => { const state = useOrderState(); return <></>;
}); const OrderPage = () => { const [state] = useState(() => new OrderState()); return ( <OrderPageProvider state={state}> <div> <ListItems /> <DeliveryMilestone /> </div> </OrderPageProvider> );
}; export default OrderPage;
To sum up, In the first series of MobX, this article serves as a basic setup for integrating MobX with React. Leveraging the combination of MobX with helpful React hooks like createContext() and useContext() to fully utilize MobX's power.
In part 2, we will explore how to update and retreive the state with MobX. Thank you for reading and see you again in the next installment 🫠🫠🫠