---
# System prepended metadata

title: State Management in React

---


# State Management in React

---

## 🟢 Introduction
- **Variable** → Just a normal JS variable. Changes do not trigger re-render and are reset on every render.  
- **State** → Managed by React (`useState` / `useReducer`). Changes trigger re-render and persist between renders.  

**Key Difference:**  
- Variables store data temporarily, but React doesn’t track them.  
- State is reactive — when it changes, React updates the UI automatically.  
---

- **Local state** → belongs to one component.  
- **Global state** → shared across components.  

Why do we need state management?


---

## 💡 Why Do We Need State Management?

### 🔐 Auth (User Login)
**Case:**
- App with pages: Login, Profile, Dashboard.  
- After login, every component must know the user is authenticated.  

**Without State Management:**
- Pass `isLoggedIn` and `user` props down through many components (prop drilling).  
- Hard to track and maintain changes.  

**With State Management:**
- Auth state stored in **Context/Redux store**.  
- Any component can access it easily.  
- Example: Navbar switches automatically from "Login" → "Logout".  

---

### 🎨 Theme (Dark/Light Mode)
**Case:**
- User toggles theme (dark ↔ light).  
- Change must apply across the entire app.  

**Without State Management:**
- Pass `theme` prop everywhere.  
- Multiple changes needed across many files.  

**With State Management:**
- Theme stored in **Context**.  
- Any component can read theme with `useContext`.  
- One toggle updates the entire UI.  

---

### 🛒 Cart (Shopping Items)
**Case:**
- E-commerce app: User adds products from Product page.  
- Cart in Navbar must show item count, Cart page must list items.  

**Without State Management:**
- Pass `cartItems` props through Product Page → Navbar → Cart Page.  
- Complex add/remove logic.  

**With State Management:**
- Cart stored in **Redux/Zustand**.  
- Product Page → `dispatch(addItem)`.  
- Navbar + Cart Page read from the same source.  
- Everything stays in sync without prop drilling.  


---

## 🟠 useState (Local State)
**Simple hook for managing state**

```jsx
const [count, setCount] = useState(0);

<button onClick={() => setCount(count + 1)}>
  Count: {count}
</button>
```

✅ Easy and quick  
❌ Hard to share between components  

**Flow:**  
```
User Action → setState() → Component Rerender → UI Updates
```

---

## 🟡 useRef (Persistent Values)
**1 - Keeps values across renders without causing rerender**

```jsx
const inputRef = useRef();

function focusInput() {
  inputRef.current.focus();
}

<input ref={inputRef} />
<button onClick={focusInput}>Focus</button>
```

- Store DOM references  
- Keep values (previous value, timer IDs)  

**Flow:**  
```
Value stored in .current → Doesn't trigger rerender → Can be accessed anytime
```

**2 - Storing a value without causing rerender**
**3. Avoiding re-creating expensive objects**

👉 Example: Keep a WebSocket connection stable.
```jsx
import { useEffect, useRef } from "react";

function Chat() {
  const socketRef = useRef(null);

  useEffect(() => {
    socketRef.current = new WebSocket("wss://example.com");
    return () => socketRef.current.close();
  }, []);

  return <p>Chat connected...</p>;
}
```

📌 Scenario: In a chat app, you want one persistent WebSocket connection that doesn’t get recreated on every render.


---

## 🔵 useReducer (Complex State)
**Best for complex state logic**

```jsx
function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, action.payload];
    default:
      return state;
  }
}

const [todos, dispatch] = useReducer(reducer, []);
dispatch({ type: 'add', payload: 'Learn React' });
```

✅ Great for large apps (forms, todos)  

**Flow:**  
```
dispatch(action) → reducer(state, action) → newState → UI Updates
```
Example

```jsx
import React, { useReducer } from "react";

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
    </div>
  );
}

export default Counter;

```
---

## 🟣 useContext (Global State Light)
**Solves prop drilling**

```jsx
const ThemeContext = createContext();

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Child />
    </ThemeContext.Provider>
  );
}

function Child() {
  const theme = useContext(ThemeContext);
  return <p>Theme: {theme}</p>;
}
```

✅ Easy to use  
❌ Causes rerenders in big apps  

### Practical Example: Auth Context

```jsx
// AuthProvider.js
import React, { createContext, useState } from "react";

export const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (username) => setUser({ name: username });
  const logout = () => setUser(null);

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}
```

```jsx
// Navbar.js
import React, { useContext } from "react";
import { AuthContext } from "./AuthProvider";

function Navbar() {
  const { user, login, logout } = useContext(AuthContext);

  return (
    <nav>
      {user ? (
        <>
          <span>Welcome, {user.name} 👋</span>
          <button onClick={logout}>Logout</button>
        </>
      ) : (
        <button onClick={() => login("Abdullah")}>Login</button>
      )}
    </nav>
  );
}
```

```jsx
// App.js
import React from "react";
import { AuthProvider } from "./AuthProvider";
import Navbar from "./Navbar";

export default function App() {
  return (
    <AuthProvider>
      <Navbar />
      <h1>My Application</h1>
    </AuthProvider>
  );
}
```

✅ Easy to use for auth, theme, language
❌ Causes unnecessary re-renders in large apps


**Flow:**  
```
Provider (value) → Context → useContext() → Consumer gets value
```

---

## 🔴 Redux (Predictable Global State)

**Core Concepts**
- **Store** → holds the state  
- **Reducer** → defines how state changes  
- **Action** → describes what happened  
- **Dispatch** → sends the action  
- **Selector** → gets the data  

```jsx
// Slice (Redux Toolkit)
const counterSlice = createSlice({
  name: 'counter',
  initialState: 0,
  reducers: {
    increment: (state) => state + 1,
  },
});

dispatch(increment());
```

✅ Predictable + DevTools  
✅ Huge ecosystem (middlewares, persistence)  

**Flow:**  
```
Dispatch(Action) → Reducer → Store Updates → Components Re-render
```

---

## ⚪️ Other Libraries
- **Zustand** → Minimal, simple API  
- **Jotai** → Atomic state  
- **React Query (TanStack)** → For server state (API data, caching, syncing)  
- **MobX** → Classic reactive state library  

---

## 📝 Summary
- **useState** → simple local state  
- **useRef** → store values without rerender  
- **useReducer** → complex state logic  
- **useContext** → light global state  
- **Redux** → advanced global state management  
- **Zustand/Jotai** → modern and simple libraries  
- **React Query** → server state management  

