- State is an object that holds dynamic data for a component.
- When state changes, React re-renders the component.
- State can be managed at:
- Local level → within a component.
- Global level → shared between components.
🔹 Ways to Manage State in React
1️⃣ Local State (useState / useReducer)
- Used for managing component-specific states.
import React, { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
👉 Local state is enough for small apps.
2️⃣ Lifting State Up (Props drilling)
- Pass state from parent → child using props.
function Child({ count }) {
return <h3>Child Count: {count}</h3>;
}
export default function App() {
const [count, setCount] = useState(0);
return (
<div>
<Child count={count} />
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
👉 Simple but can lead to prop drilling (too many props passing down).
3️⃣ Context API
- Used for avoiding prop drilling.
- Provides global state that any component can consume.
import React, { createContext, useContext, useState } from "react";
const CounterContext = createContext();
function Child() {
const { count } = useContext(CounterContext);
return <h3>Child Count: {count}</h3>;
}
export default function App() {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
<Child />
<button onClick={() => setCount(count + 1)}>Increase</button>
</CounterContext.Provider>
);
}
👉 Good for small/medium apps, but can cause unnecessary re-renders if misused.
4️⃣ State Management Libraries (Redux, Zustand, MobX, Recoil)
- Used for large-scale apps where many components share state.
Example with Redux Toolkit:
// counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; }
}
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
// App.js
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import { increment } from "./counterSlice";
export default function App() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
return (
<div>
<h2>Count: {count}</h2>
<button onClick={() => dispatch(increment())}>Increment</button>
</div>
);
}
👉 Redux provides predictable state management for big apps.
5️⃣ Server State (React Query / Apollo)
- When state comes from a backend API.
- Tools like React Query manage caching, refetching, and synchronization with server.
import { useQuery } from "@tanstack/react-query";
function Users() {
const { data, isLoading } = useQuery(["users"], () =>
fetch("https://jsonplaceholder.typicode.com/users").then(res => res.json())
);
if (isLoading) return <h3>Loading...</h3>;
return (
<ul>
{data.map(user => <li key={user.id}>{user.name}</li>)}
</ul>
);
}
👉 Best for handling async data (APIs).
🔹 Summary (Interview Ready)
✅ Local state → useState, useReducer.
✅ Shared state → Lifting state up / Context API.
✅ Large-scale apps → Redux, Zustand, MobX, Recoil.
✅ Server state → React Query, Apollo.