As React applications grow, managing state becomes increasingly challenging. While useState() is enough for small components, larger applications often need to share data across many unrelated components.
Consider an e-commerce application. The user's authentication status, shopping cart, selected theme, notifications, and cached API data may all be needed by multiple parts of the application.
Without proper state management, developers must continuously pass data through many intermediate components, making applications difficult to maintain.
In this module, you'll learn:
State Management is the process of storing, updating, and sharing data across an application.
Think of state as the application's memory.
Whenever something changes while your application is running, that information is stored inside state.
For example, when a user logs in:
User Logs In
│
▼
Store User Information
│
├────────► Navbar
├────────► Dashboard
├────────► Profile
└────────► Settings
Instead of every component storing its own copy of the user information, all components share one source of truth.
Without state management:
App
│
▼
Parent
│
▼
Child
│
▼
GrandChild
Data must be passed through every component.
This is called Props Drilling.
With state management:
Global Store
│
├────────► Navbar
├────────► Dashboard
├────────► Profile
└────────► Settings
Every component can directly access the shared data.
Not every piece of data needs to be shared.
React applications generally use two kinds of state.
Local state belongs to a single component.
const [count, setCount] = useState(0);
Only that component can access or modify the value.
Example:
Counter Component
│
▼
count = 5
No other component knows about this value.
Local state is perfect for:
Global state is shared across multiple components.
Global Store
│
├────────► Navbar
├────────► Sidebar
├────────► Dashboard
├────────► Profile
└────────► Checkout
Every component uses the same data.
Global state is commonly used for:
As applications become larger, developers usually progress through different state management solutions.
useState()
│
▼
Context API
│
▼
Zustand
│
▼
Jotai
│
▼
MobX
Each tool solves increasingly complex state-sharing problems.
The Context API is React's built-in solution for sharing state globally.
No external library is required.
It is perfect for sharing values like:
Imagine the following component tree:
App
│
▼
Parent
│
▼
Child
│
▼
GrandChild
If the GrandChild component needs the user object, every component must receive and forward it.
<App user={user} />
<Parent user={user} />
<Child user={user} />
<GrandChild user={user} />
Even though only the last component actually needs the data.
This repetitive passing of props is called Props Drilling.
Context removes the need to pass props through intermediate components.
Context
│
├────────► Parent
├────────► Child
└────────► GrandChild
Any component inside the provider can access the value directly.
import { createContext } from "react";
const UserContext = createContext();
createContext() creates a new context object.
Wrap your application inside a Provider.
<UserContext.Provider value="Samir">
<App />
</UserContext.Provider>
Everything inside <App /> can now access "Samir".
import { useContext } from "react";
const user = useContext(UserContext);
console.log(user);
Output:
Samir
import { createContext, useContext } from "react";
const ThemeContext = createContext();
function App() {
return (
<ThemeContext.Provider value="dark">
<Dashboard />
</ThemeContext.Provider>
);
}
function Dashboard() {
const theme = useContext(ThemeContext);
return <h2>{theme}</h2>;
}
Output
dark
Provider
│
▼
Context
│
▼
Consumer
Use Context when sharing:
Avoid using Context for frequently changing large datasets.
Zustand is a lightweight state management library designed specifically for React.
Unlike Context API, Zustand does not require a Provider.
Developers like Zustand because it provides:
npm install zustand
import { create } from "zustand";
const useStore = create((set) => ({
count: 0,
increment: () =>
set((state) => ({
count: state.count + 1
}))
}));
function Counter() {
const count = useStore((state) => state.count);
const increment = useStore((state) => state.increment);
return (
<button onClick={increment}>
{count}
</button>
);
}
Store
│
▼
Components Subscribe
│
▼
State Updates
│
▼
Only Interested Components Re-render
const useAuthStore = create((set) => ({
user: null,
login: (user) => set({ user }),
logout: () => set({ user: null })
}));
Using it:
const user = useAuthStore((state) => state.user);
Jotai is an atomic state management library.
Instead of having one large global store, Jotai stores data in independent Atoms.
Traditional Store:
Global Store
Jotai:
Atom A
Atom B
Atom C
Atom D
Each atom stores one independent piece of state.
npm install jotai
import { atom } from "jotai";
const countAtom = atom(0);
import { useAtom } from "jotai";
const [count, setCount] = useAtom(countAtom);
const countAtom = atom(0);
function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<button onClick={() => setCount(count + 1)}>
{count}
</button>
);
}
Atom
│
▼
Component
│
▼
Update Atom
│
▼
Component Re-renders
MobX is a reactive state management library.
The main philosophy is simple:
Whenever the state changes, the UI updates automatically.
State
│
▼
Observable
│
▼
Automatic Tracking
│
▼
UI Updates
npm install mobx
npm install mobx-react-lite
import { makeAutoObservable } from "mobx";
class CounterStore {
count = 0;
constructor() {
makeAutoObservable(this);
}
increment() {
this.count++;
}
}
export default new CounterStore();
import { observer } from "mobx-react-lite";
const Counter = observer(() => {
return (
<button
onClick={() => store.increment()}
>
{store.count}
</button>
);
});
Observable State
│
▼
Automatic Tracking
│
▼
Component Re-renders
| Tool | Best For |
|---|---|
| Context API | Theme, Authentication, Language |
| Zustand | Modern React Applications |
| Jotai | Atomic State, TypeScript Projects |
| MobX | Enterprise Applications |
<Provider>
<App />
</Provider>
Requires a Provider.
const count = useStore((state) => state.count);
No Provider is required.
| Feature | Zustand | Jotai |
|---|---|---|
| Store Based | ✅ | ❌ |
| Atomic State | ❌ | ✅ |
| Learning Curve | Easy | Easy |
| Popularity | Higher | Growing |
| Feature | Zustand | MobX |
|---|---|---|
| Simplicity | Very Easy | Moderate |
| React Integration | Excellent | Excellent |
| Learning Curve | Easy | Higher |
| Boilerplate | Low | Medium |
useState()
│
▼
Context API
useState()
│
▼
Zustand
Zustand
│
├────────► Jotai
└────────► MobX
Imagine building an e-commerce website.
Multiple components need the same information.
Global Store
│
├────────► Navbar
├────────► Cart
├────────► Checkout
├────────► Products
├────────► Wishlist
└────────► Profile
Every component reads from the same store.
Use useState() whenever possible.
const [count, setCount] = useState(0);
Good examples:
Reasons:
Don't introduce MobX or Jotai for:
Good
authStore
cartStore
themeStore
Bad
everythingStore
Smaller stores are easier to maintain.
| Tool | Purpose |
|---|---|
| Context API | Built-in global state |
| Provider | Supplies context |
| useContext | Consumes context |
| Zustand | Lightweight global store |
| Jotai | Atomic state management |
| MobX | Reactive state management |
| Global State | Shared application data |
| Local State | Component-specific data |
As React applications grow, their state management evolves.
Component State
│
▼
useState()
│
▼
Context API
│
▼
Zustand
│
▼
Jotai
│
▼
MobX
For most modern React applications, the most practical combination is:
useState()
+
Context API
+
Zustand
This combination provides an excellent balance of simplicity, performance, scalability, and developer experience, while Jotai and MobX are generally reserved for specialized or large-scale applications.