State is any data in your app that can change: a counter value, whether a modal is open, the logged-in user, items in a cart. "State management" is the set of decisions about where that data should live and how the components that care about it get access. Get these decisions right and a React app stays simple as it grows; get them wrong and even small apps become tangled.
This overview walks the ladder from the simplest option to the most powerful, and — just as importantly — tells you when to stop climbing. The single biggest mistake beginners make is reaching for heavy tools before they need them. If hooks are still new to you, read the React hooks introduction first, because useState is the foundation of everything here.
Level 1: Local state
The default home for state is inside the one component that uses it, via useState. If only a single component cares about a value, keep it there. There is nothing wrong with local state — it is the right answer far more often than beginners expect.
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
return (
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search courses..."
/>
);
}
export default SearchBox;
The query value matters only to SearchBox, so it lives in SearchBox. No sharing, no complexity. Start every piece of state here and only move it when a concrete need forces you to.
State vs props: the core distinction
Before sharing state, be crystal clear on the difference between state and props, because it drives every decision that follows.
| State | Props | |
|---|---|---|
| Owned by | The component itself | The parent |
| Mutable? | Yes, via its setter | No, read-only |
| Direction | Lives locally | Flows down |
| Purpose | Data that changes | Configuration passed in |
Props flow down from parent to child. State can be lifted up so that a parent owns it and passes it down to children as props. That single mechanism — lifting state up — solves most sharing problems without any library.
Level 2: Lifting state up
When two sibling components need the same data, neither can own it, because siblings cannot pass props to each other. The fix is to move the state to their nearest common parent and pass it down to both.
import { useState } from "react";
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<Display count={count} />
<Controls onIncrement={() => setCount(count + 1)} />
</>
);
}
function Display({ count }) {
return <p>Total: {count}</p>;
}
function Controls({ onIncrement }) {
return <button onClick={onIncrement}>Add one</button>;
}
export default Parent;
The parent owns count. Display receives it as a prop to show it; Controls receives a callback to change it. Both siblings now stay in sync through their shared parent. This pattern handles a surprising amount of real-world state sharing.
Pro tip: Put state as low in the tree as possible while still being above every component that needs it. State too high causes needless re-renders of unrelated components; state too low means you cannot share it. "As low as possible, as high as necessary" is the guiding rule.
The prop drilling problem
Lifting state up works well until the components that need the data are separated by many layers. Then you end up passing a prop through five intermediate components that do not use it themselves, just to reach a deeply nested child. This is called prop drilling, and it makes code noisy and fragile.
// user is passed through every level just to reach Avatar
<Page user={user}>
<Sidebar user={user}>
<Menu user={user}>
<Avatar user={user} />
Nothing is broken here, but it is tedious and every intermediate component is coupled to data it does not care about. This is the exact problem the Context API was built to solve.
Level 3: The Context API
Context lets you create a shared value that any descendant component can read directly, no matter how deep, without passing props through each level. It is built into React — no extra library needed.
import { createContext, useContext } from "react";
const UserContext = createContext(null);
function App() {
const user = { name: "Samyuktha", role: "student" };
return (
<UserContext.Provider value={user}>
<Profile />
</UserContext.Provider>
);
}
function Profile() {
const user = useContext(UserContext);
return <p>Welcome, {user.name}</p>;
}
export default App;
A Provider at the top makes the value available; any descendant calls useContext to read it. Prop drilling disappears. Context is ideal for low-frequency global data: the current user, theme, language, or authentication status.
Common mistake: Treating Context as a full state library and putting fast-changing state in it. Every component consuming a context re-renders when its value changes, so high-frequency updates in a large tree can hurt performance. Keep Context for stable, app-wide data, and split unrelated concerns into separate contexts.
Level 4: Dedicated libraries
For large applications — many developers, complex shared state that changes often, elaborate update logic — a purpose-built library earns its keep. The common choices:
- Redux (with Redux Toolkit): the long-standing standard for big apps. Predictable, centralized, with excellent debugging tools. More boilerplate, but the structure pays off at scale.
- Zustand: a lightweight, minimal-boilerplate store. Popular for teams that want global state without Redux's ceremony.
- React Query / TanStack Query: technically server-state management. If most of your "state" is really data fetched from an API, this handles caching, refetching, and loading states far better than manual
useEffectcalls.
The honest guidance: you do not need any of these to learn React or to build most projects. Beginners who add Redux on day one usually write more code to accomplish less. Learn useState, lifting state up, and Context thoroughly first — they cover the vast majority of cases, and interviewers expect you to know them cold.
Interview note: A frequent question is "When would you choose Redux over Context?" A strong answer: Context solves prop drilling for relatively stable global values, while Redux (or Zustand) is for large apps with complex, frequently updated shared state that benefits from a single predictable store and dev tooling. Show you know Context is not automatically worse — it is a different tool. More fresher questions like this are collected in the React freshers interview guide.
Server state is different
One distinction saves beginners enormous pain: most data in a real app is not really "state" in the classic sense — it is a copy of data that lives on a server. The list of courses, the user's profile, yesterday's orders: these come from an API and can go stale.
Treating server data like local UI state leads to a familiar mess of manual useEffect fetches, loading flags, error flags, and cache-invalidation bugs. Tools like React Query exist precisely because this problem is common and hard to do well by hand. They cache responses, dedupe requests, refetch in the background, and give you isLoading and isError for free.
The practical takeaway: separate your two kinds of state. UI state (is the menu open, which tab is selected) belongs in useState or Context. Server state (data fetched from an API) is often best handled by a data-fetching library once your app grows past a few screens. Blurring the two is one of the most common architectural mistakes in beginner React projects.
A decision checklist
When you have a piece of state and are not sure where it belongs, ask in order:
- Does only one component use it? Keep it local with
useState. - Do a few nearby components need it? Lift it up to their common parent.
- Do many components across the tree need stable shared data? Use Context.
- Is it a large app with complex, high-frequency shared state? Consider Redux or Zustand.
- Is the state actually server data? Use React Query instead of hand-rolling it.
Following this ladder keeps your apps as simple as they can be while still handling real complexity when it arrives.
Where this fits in your learning
State management ties directly into forms, since a controlled input is just state you display and update — see controlled components in React forms to apply these ideas to real user input. It also underpins routing, theming, and authentication in bigger apps.
Do not try to memorize every library. Instead, build a small app with local state, hit a real sharing problem, solve it by lifting state up, hit prop drilling, and solve that with Context. Feeling each problem before you learn its solution is what makes the concepts stick. When you want the guided sequence with milestones, the React learning hub lays it out, and what React is is worth a revisit to see how the whole model fits together.
Frequently Asked Questions
What is state management in React?
What is prop drilling and why is it a problem?
When should I use Context instead of useState?
Do I need Redux to build a React app?
What is the difference between state and props?
Want to Build Your Career in Java Full Stack with AI?
Join CodeBegun and train with working industry engineers — Explore the Program

