Mahamudul Hasan Rubel
HomeAboutProjectsSkillsExperienceBlogPhotosContact
Mahamudul Hasan Rubel

Senior Software Engineer crafting high-performance web applications and SaaS platforms.

Navigation

  • Home
  • About
  • Projects
  • Skills
  • Experience
  • Blog
  • Photos
  • Contact

Get in Touch

Available for senior/lead roles and consulting.

bd.mhrubel@gmail.comHire Me

© 2026 Mahamudul Hasan Rubel. All rights reserved.

Built with using Next.js 16 & Tailwind v4

Back to Blog
ReactNext.jsJune 22, 20264 min read

React state management: Mapping Your Next.js Component Hierarchy

React state management starts with a solid component hierarchy. Learn how to map data flow and own your state in Next.js to prevent complex bugs.

ReactNext.jsFrontendArchitectureState ManagementTutorial

I remember staring at a 400-line Dashboard.tsx file in a project last year, watching state variables pile up like unwashed dishes in a sink. Every time I added a new toggle or filter, I had to pass five levels of props down to a deep-nested Chart component. It was a mess. That’s when I realized my component hierarchy wasn't just a folder structure—it was a blueprint for my entire application's performance.

If you don't map your data flow early, you'll end up fighting your own architecture.

Why Component Hierarchy Matters for Data Flow

In React, data flows one way: top to bottom. It’s elegant until you have to move data from a deep child back to a sibling. When I first started, I tried to solve this by creating "God components" that held every piece of state at the top level. That was a mistake. It caused the entire app to re-render every time a user typed a single character into a search bar.

Understanding React state management and the unidirectional data flow is the first step toward writing cleaner code. If your state is trapped at the wrong level of your tree, your components will become brittle and impossible to test.

Finding the Right Home for State

Before you write a single useState hook, ask yourself: Who actually needs this data? If only two sibling components need a value, don't move it to the parent. Use a shared parent one level up, or consider composition.

When I refactored that messy dashboard, I stopped trying to centralize everything. I moved state closer to the components that consumed it. If a component is responsible for its own state, it’s easier to isolate during debugging. If you’re struggling with complex UI interactions, you should also look into how React rendering: Mastering State Batching and the Two-Pass Model affects these updates.

The "Lifting State Up" Strategy

"Lifting state up" is the classic solution, but use it sparingly. Here’s a quick mental check:

  1. Identify the common ancestor of all components that need the state.
  2. Move the state to that ancestor.
  3. Pass the state down as props, and pass the state-setter functions down to handle updates.

If you find yourself passing props through three or more layers of components that don't need them—that’s prop drilling. Stop. That’s a sign you need to use composition (passing components as children) or a context provider.

Next.js Architecture and Server Components

Next.js adds a layer of complexity with Server Components. Since Server Components don't support useState or useEffect, you’re forced to think differently about your Next.js architecture.

I’ve found it helpful to split my UI into two distinct types:

  • Server-rendered containers: These handle data fetching and pass props to interactive children.
  • Client-side islands: These are the specific components marked with 'use client' that manage the UI state.

When you mix these correctly, you don't just get better performance; you get a clear boundary between where data comes from and how it's manipulated. If you're dealing with hydration errors, it's often because your state reconciliation is fighting against the static server output. I've written before about how Next.js Server Components Hydration: Solving State Reconciliation Issues can be a headache if you don't keep your client/server boundaries clean.

A Practical Example

Think of a search page. You have a search input, a filter dropdown, and a results list.

TSX
// The "Smart" Container (Client Component)
CE9178">'use client';
export default function SearchPage() {
  const [query, setQuery] = useState(CE9178">'');
  const [results, setResults] = useState([]);

  return (
    <>
      <SearchInput onSearch={setQuery} />
      <ResultsList data={results} />
    </>
  );
}

In this setup, SearchPage owns the state. SearchInput doesn't need to know about the results, and ResultsList doesn't need to know about the search query. They are decoupled. This is the goal.

FAQ: Common Pitfalls

Q: When should I use Context instead of lifting state up? A: Use Context only when data needs to be accessed by many components at different levels (like themes, user authentication, or language settings). If you use it for everything, you'll trigger unnecessary re-renders across your entire app.

Q: How do I know if my component hierarchy is too deep? A: If you’re passing the same prop through four levels of components just to get it to the fifth, your hierarchy is too deep. Break it up or use a pattern like component composition to inject the child directly into the parent.

Q: Is it okay to keep state in a global store like Zustand or Redux? A: It’s fine, but don't use it as a crutch. Start with local state, then lift it up, and reach for a global store only when you genuinely have data that needs to be accessed from disconnected parts of your app.

Mapping your component tree isn't a one-time task. I still find myself refactoring components after I've written them—sometimes a state logic that seemed perfect on Monday feels clunky by Wednesday. Don't be afraid to move things around. The goal isn't to get it perfect on the first try; it's to keep your dependencies clear and your data flow predictable.

Back to Blog

Similar Posts

ReactNext.jsJune 22, 20264 min read

React rendering: Mastering State Batching and the Two-Pass Model

React rendering follows a two-pass mental model involving state batching and reconciliation. Learn how React Fiber manages the render and commit phases.

Read more
ReactNext.js
June 22, 2026
4 min read

Next.js Server Components: Architecting Resilient Data Fetching Pipelines

Next.js Server Components require robust data fetching strategies. Learn how to use AsyncLocalStorage and request-scoped caching to build resilient architectures.

Read more
ReactNext.jsJune 22, 20264 min read

React derived state: Stop using useEffect for data calculations

React derived state is the key to faster components. Learn how to stop abusing useEffect for data transformations and simplify your React performance optimization.

Read more