State Colocation in React JS
State Colocation is a React design principle in which state is kept as close as possible to the component that uses it. Instead of storing every state variable in a high-level parent component or global store, developers keep local state inside the component or the smallest component group that needs it.
This approach helps organize code, reduces unnecessary state sharing, and can limit how much of the component tree needs to re-render when local state changes.
Course Links: JustAcademy React JS Training Course | Register for React JS Course Demo
1. What Is State Colocation?
In React, state stores information that can change over time and affect the rendered UI. State colocation means placing that state inside the component that needs it, or in the nearest common component when multiple components must share it.
For example, if a search box only needs to remember the text typed into it, the search text can live inside the Search component instead of the entire application component.
Simple Example
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
return (
<div>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search..."
/>
<p>Search query: {query}</p>
</div>
);
}
export default SearchBox;
Here, the query state belongs to SearchBox because that component is responsible for reading and displaying the search text.
2. Why Is State Colocation Important?
- Better code organization: Keep related state and logic together.
- Reduced unnecessary sharing: Avoid passing local state through unrelated components.
- Potential performance improvements: Local updates do not require a high-level parent to re-render merely because that state changed.
- Easier maintenance: Changes to a component's internal behavior are less likely to affect unrelated parts of the application.
- Less prop drilling: State that is only used locally does not need to be passed through multiple component layers.
- Clear ownership: It becomes easier to understand which component manages a particular value.
- Simpler testing: Components with self-contained state and behavior are often easier to test.
3. How State Colocation Works
- Identify the state variable that a component needs.
- Find every component that reads or updates that state.
- If only one component needs it, keep the state inside that component.
- If multiple related components need it, place the state in their nearest common parent.
- Pass the necessary values and event handlers to child components through props.
- Use Context or another shared-state solution only when it provides a clear benefit.
Basic Structure
App
├── Header
├── ProductSection
│ ├── ProductCard
│ └── ProductCard
└── SearchBox
├── Search Input State
└── Search Input UI
In this example, the search input state can stay inside SearchBox if no other component needs it. If the search query must filter products in ProductSection, the state may need to move to their nearest common parent or be coordinated through another appropriate approach.
4. State Colocation vs. Lifting State Up
Lifting state up means moving state from child components to a shared parent so multiple components can coordinate. State colocation means keeping state near the components that use it. These are complementary principles, not opposing rules.
| Feature |
State Colocation |
Lifting State Up |
| State location |
Near the component or small group that uses it. |
In the nearest common parent of the components that share it. |
| Main purpose |
Keep local behavior self-contained. |
Coordinate state across multiple components. |
| Data sharing |
Often needs little or no prop passing. |
Usually passes values and handlers through props. |
| Example |
Input text used only inside a search box. |
Selected tab used by multiple tab panels. |
| Best approach |
When state is only needed locally. |
When multiple components must stay in sync. |
Rule of thumb: Keep state as low as possible, but as high as necessary for every component that needs to coordinate around it.
5. Example: State Stored Too High in the Component Tree
In the following example, the App component owns the search input state even though only the SearchBox uses it. Every time the query changes, App renders again, which can cause its child components to render again as well.
import { useState } from "react";
function SearchBox({ query, setQuery }) {
return (
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search..."
/>
);
}
function Header() {
console.log("Header rendered");
return <h1>My Application</h1>;
}
function ProductList() {
console.log("ProductList rendered");
return <p>Product list goes here.</p>;
}
function App() {
const [query, setQuery] = useState("");
return (
<div>
<Header />
<SearchBox query={query} setQuery={setQuery} />
<ProductList />
</div>
);
}
export default App;
When the query changes, the state owner, App, renders again. React may also call the child components again as part of rendering the updated tree. This can be unnecessary when the other components do not depend on the query.
6. Improved Example: Colocating State
Move the query state into SearchBox if no other component needs to read it.
import { useState } from "react";
function SearchBox() {
const [query, setQuery] = useState("");
return (
<div>
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder="Search..."
/>
<p>Searching for: {query}</p>
</div>
);
}
function Header() {
console.log("Header rendered");
return <h1>My Application</h1>;
}
function ProductList() {
console.log("ProductList rendered");
return <p>Product list goes here.</p>;
}
function App() {
return (
<div>
<Header />
<SearchBox />
<ProductList />
</div>
);
}
export default App;
Now typing in the search box updates its local state. App does not need to render again just because the query changed, and its sibling components are not re-rendered as a consequence of that local state update.
Important: State colocation can reduce unnecessary rendering, but it does not guarantee that a component will never re-render. Parent updates, changed props, context updates, and other state changes can still trigger renders.
7. When Should State Be Colocated?
Keep state local when it is only needed for a component's internal behavior or a small subtree.
- Text entered into a search field that is used only within that field.
- Whether a tooltip is visible.
- Whether a dropdown menu is open.
- Whether a password field displays or hides its text.
- Temporary form values that are not shared with other components.
- Whether a local modal is open.
- Current step in a self-contained multi-step widget.
- Expanded or collapsed state for an independent accordion item.
8. When Should State Be Lifted Up Instead?
Do not colocate state so deeply that other components cannot access the information they need. If multiple components must read or update the same value, move it to the nearest common parent or use an appropriate shared-state design.
- Shared form state: Multiple fields contribute to one form submission or validation process.
- Tabs: Tab buttons and panels need to agree on which tab is active.
- Shopping cart: Product cards, the cart icon, and the checkout view need coordinated cart data.
- Search results: The search field and results list both depend on the same query.
- Shared filters: Multiple product sections use the same filter selection.
- Sibling components: Two components must update in response to the same user action.
Example: Shared State in a Parent
import { useState } from "react";
function SearchInput({ query, onQueryChange }) {
return (
<input
value={query}
onChange={(event) => onQueryChange(event.target.value)}
placeholder="Search products..."
/>
);
}
function ProductResults({ query }) {
const products = ["Laptop", "Phone", "Headphones"];
const filteredProducts = products.filter((product) =>
product.toLowerCase().includes(query.toLowerCase())
);
return (
<ul>
{filteredProducts.map((product) => (
<li key={product}>{product}</li>
))}
</ul>
);
}
function App() {
const [query, setQuery] = useState("");
return (
<div>
<SearchInput query={query} onQueryChange={setQuery} />
<ProductResults query={query} />
</div>
);
}
export default App;
Here, the query is shared between the input and results list, so it belongs in their common parent. The input reports changes to the parent, and the results list receives the updated query.
9. State Colocation and Component Re-renders
In React, when a component's state changes, React renders the component again to calculate the updated UI. The location of the state therefore affects which component owns the update and can influence how much of the tree participates in rendering.
| State Placement |
Typical Effect |
| State in a high-level parent |
That parent renders when the state changes; child components may also render again. |
| State in a leaf component |
The leaf component can update without requiring its parent to render because of that state change. |
| State in a shared parent |
Useful when sibling components must coordinate around the same value. |
| State in a context provider |
Useful for shared data, but consumers can render when the provided value changes. |
State colocation can improve performance by narrowing the scope of updates without needing memoization everywhere. However, rendering behavior depends on the component tree, props, context, and React's rendering process. Measure performance before applying additional optimizations.
10. State Colocation with Forms
Forms often contain both local and shared state. A field's temporary focus or visibility state may belong inside the field component, while values needed for validation or submission may belong in a form component.
Example: Password Visibility
import { useState } from "react";
function PasswordInput() {
const [showPassword, setShowPassword] = useState(false);
return (
<div>
<label htmlFor="password">Password:</label>
<input
id="password"
type={showPassword ? "text" : "password"}
/>
<button
type="button"
onClick={() => setShowPassword((current) => !current)}
>
{showPassword ? "Hide" : "Show"} password
</button>
</div>
);
}
export default PasswordInput;
The showPassword state is colocated with PasswordInput because it controls only that field's display behavior. If the entire form needs to control this behavior, it can instead be lifted to the form parent.
11. State Colocation vs. Global State
Global state is useful when information needs to be shared across distant components or across multiple application areas. However, not every state variable needs to live in a global store.
| Feature |
Local State |
Global or Shared State |
| Scope |
A component or small subtree. |
Multiple distant components or application areas. |
| Examples |
Tooltip visibility, local input, dropdown open state. |
Authenticated user, shared cart, application-wide preferences. |
| Complexity |
Usually simpler to manage. |
May require Context, a reducer, or a state management library. |
| Data access |
Accessed directly within the component. |
Accessed through a shared mechanism. |
| Best practice |
Keep local state local when possible. |
Use shared state when there is a genuine cross-component need. |
12. State Colocation and Derived State
State colocation is also about avoiding unnecessary state variables. If a value can be calculated from existing state or props during rendering, it often does not need a separate state variable.
Incorrect Example: Redundant State
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [fullName, setFullName] = useState("");
This design stores the full name separately even though it can be calculated from the first and last names. Keeping duplicate state creates more opportunities for inconsistent values.
Improved Example
import { useState } from "react";
function NameForm() {
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const fullName = `${firstName} ${lastName}`.trim();
return (
<div>
<input
value={firstName}
onChange={(event) => setFirstName(event.target.value)}
placeholder="First name"
/>
<input
value={lastName}
onChange={(event) => setLastName(event.target.value)}
placeholder="Last name"
/>
<p>Full name: {fullName}</p>
</div>
);
}
export default NameForm;
The full name is derived during rendering, so there is no separate state variable to keep synchronized.
13. Common Mistakes in State Colocation
| Mistake |
Why It Is a Problem |
Solution |
| Keeping all state in App. |
Unrelated UI state becomes coupled to a high-level component. |
Move state into the smallest component that needs it. |
| Keeping shared state too low. |
Other components cannot easily coordinate around the value. |
Lift it to the nearest common parent. |
| Using global state for simple local interactions. |
It adds unnecessary complexity and shared dependencies. |
Use local state for local UI behavior. |
| Duplicating the same value in multiple state variables. |
The copies can become inconsistent. |
Keep one source of truth and derive values where possible. |
| Mirroring props into state unnecessarily. |
The state may become outdated when props change. |
Use the prop directly unless an independent initial value is intentional. |
| Moving state solely to avoid every re-render. |
It may make data flow harder to understand. |
Balance clear ownership with measured performance needs. |
| Adding memoization before profiling. |
It increases complexity without necessarily improving performance. |
Start with appropriate state placement and measure if optimization is needed. |
14. Best Practices for State Colocation
- Keep state close to the component that owns the behavior.
- Lift state only when another component needs to read or update it.
- Use the nearest common parent for coordinated sibling state.
- Keep a single source of truth for each piece of shared information.
- Avoid storing derived values that can be calculated from existing state or props.
- Do not introduce a global state library for every small interaction.
- Use Context when many descendants need the same information and prop passing becomes inconvenient.
- Keep state update logic close to the state owner where practical.
- Use stable keys when rendering lists and when component identity should change.
- Profile complex interfaces before adding performance optimizations.
- Prioritize readable and maintainable data flow over premature optimization.
15. Practical Project: Product Search Interface
Build a small product search interface to practice state colocation and shared state.
Requirements
- Create a SearchBox component with a controlled input.
- Create a ProductList component to display products.
- Keep the query local if it is only displayed inside SearchBox.
- Move the query to the nearest common parent if it must filter ProductList.
- Keep the dropdown-open state local if only the search dropdown uses it.
- Show a message when no products match.
- Calculate filtered results from the products and query instead of storing duplicate filtered state.
- Use stable product IDs as list keys.
Suggested Component Structure
ProductSearch/
├── App.jsx
├── SearchBox.jsx
├── ProductList.jsx
├── ProductCard.jsx
├── ProductSearch.css
└── ProductSearch.test.jsx
Expected Learning Outcomes
- Understand where state should live in a component tree.
- Practice lifting shared state when multiple components need it.
- Separate local interaction state from shared application data.
- Reduce redundant state and unnecessary component coupling.
- Learn how state placement can affect rendering behavior.
16. Interview Questions and Answers
Q1. What is state colocation in React?
State colocation is the practice of keeping state close to the component or smallest component group that needs to read or update it.
Q2. Why is state colocation useful?
It improves code organization, reduces unnecessary state sharing, clarifies ownership, and can reduce the scope of re-renders.
Q3. What is the difference between state colocation and lifting state up?
State colocation keeps state local, while lifting state up moves state to a common parent when multiple components need to coordinate around it.
Q4. Should all React state be stored in App?
No. State should live where it is needed. App should own only state that it or its descendants need to coordinate.
Q5. Does state colocation eliminate all re-renders?
No. It can prevent a local state update from requiring its parent to render, but other updates can still cause components to render again.
Q6. When should state be lifted up?
Lift state when multiple components need access to the same value or must stay synchronized.
Q7. What is the nearest common parent?
It is the closest component in the component tree that contains all components that need to share the state.
Q8. Is local state better than global state?
Neither is always better. Local state is appropriate for isolated interactions, while global or shared state is useful for data needed across distant components.
Q9. What is derived state?
Derived state is information calculated from existing props or state. It often does not need its own state variable.
Q10. How does state colocation help performance?
Keeping local state low in the tree can avoid re-rendering a high-level parent solely because of that local state update, reducing the scope of rendering work.
Q11. Can state colocation reduce prop drilling?
Yes. If state is only used locally, it does not need to pass through unrelated components. Shared state may still require props or Context.
Q12. When should Context be used?
Use Context when multiple descendants need shared information and passing it through intermediate components becomes inconvenient.
17. Practice Exercises
- Create a counter component with its own local state.
- Create a password field with locally managed visibility state.
- Build a search box whose query is used only within the component.
- Extend the search box so it filters a sibling product list, lifting state to the nearest common parent.
- Create two tabs that share one active-tab state.
- Refactor a component that stores the same value in multiple state variables.
- Move a dropdown's open state from App into the dropdown component.
- Inspect component renders before and after colocating state.
18. Quick Revision
- State colocation means keeping state near the component that uses it.
- Local interactions should generally use local state.
- Shared state should live in the nearest common parent or an appropriate shared-state mechanism.
- Lifting state up and state colocation work together.
- Local state updates do not require the parent to render solely because that local state changed.
- State colocation can improve maintainability and limit unnecessary rendering work.
- Avoid duplicate state and calculate derived values when possible.
- Use Context or global state only when the sharing requirement justifies it.
19. Official Learning Resources
20. Conclusion
State colocation is an important React design principle that helps developers decide where state should live. Keeping state close to its usage makes components easier to understand and maintain, while lifting state up allows related components to coordinate when necessary.
The best approach is to keep state as local as possible without preventing components from sharing information they genuinely need. This creates clearer data flow, reduces unnecessary coupling, and can improve rendering efficiency.
Learn More: JustAcademy React JS Training Course | Register for React JS Course Demo