Skip to main content

Thinking in React: Component Hierarchy and State

"Thinking in React" is a systematic approach to designing React applications by breaking a UI design into a logical component hierarchy and identifying the minimal state needed. The first step involves deconstructing your design into components, building a static version that renders data without interactivity, and then pinpointing exactly which pieces of data will change over time. This methodology transforms you from a coder following syntax to an architect designing application structure.

📖 Introduction

Building on inverse data flow (Part 2), we've learned the mechanics of React: creating components, managing state with useState, and passing data with props and callbacks. Now, it's time to elevate our skills from mechanics to strategy. How do you look at a design mockup and decide which components to build? How do you determine where state should live? This is the art of "Thinking in React."

This article, the first of a two-part series, will guide you through the initial, crucial steps of this process, transforming you from a coder into an architect.


📚 Prerequisites

To get the most out of this guide, you should be comfortable with:

  • Creating React components
  • Using props to pass data from parent to child
  • Basic JavaScript data structures (arrays and objects)

🎯 Article Outline: What You'll Master

In this article, we will begin building a simple, searchable product table, focusing on the first three steps of the "Thinking in React" methodology:

  • Step 1: Deconstruct the UI: How to look at a design and break it down into a logical component hierarchy.
  • Step 2: Build a Static Foundation: Creating a non-interactive version of the app that renders the UI from a fixed data model.
  • Step 3: Pinpoint the State: Identifying the absolute minimum pieces of data that need to change over time.

🧠 Section 1: The Goal: A Searchable Product Table

Imagine we've been given a mockup for an app that displays a list of products. The app needs to allow users to search the product list and also filter by whether the product is in stock.

Here's our mockup:

+-----------------------------------------+
| Search... [ ] |
| [ ] Only show products in stock |
+-----------------------------------------+
| Name | Price |
+-----------------------------------------+
| Football | $49.99 |
| Baseball | $9.99 |
| Basketball | $29.99 |
| iPod Touch | $99.99 |
| iPhone 5 | $399.99 |
| Nexus 7 | $199.99 |
+-----------------------------------------+

Our data will look something like this:

[
{ "category": "Sporting Goods", "price": "$49.99", "stocked": true, "name": "Football" },
{ "category": "Sporting Goods", "price": "$9.99", "stocked": true, "name": "Baseball" },
{ "category": "Sporting Goods", "price": "$29.99", "stocked": false, "name": "Basketball" },
{ "category": "Electronics", "price": "$99.99", "stocked": true, "name": "iPod Touch" },
{ "category": "Electronics", "price": "$399.99", "stocked": false, "name": "iPhone 5" },
{ "category": "Electronics", "price": "$199.99", "stocked": true, "name": "Nexus 7" }
]

💻 Section 2: Step 1 - Break the UI into a Component Hierarchy

The first step is to draw boxes around every component and subcomponent in the mockup and give them names. We'll use the single responsibility principle: each component should ideally do only one thing.

  1. FilterableProductTable (Orange): The entire application.
  2. SearchBar (Blue): The search input and checkbox.
  3. ProductTable (Green): The table of products.
  4. ProductCategoryRow (Turquoise): The "Sporting Goods" and "Electronics" headers.
  5. ProductRow (Red): Each individual product row.

This gives us a clear hierarchy:

  • FilterableProductTable
    • SearchBar
    • ProductTable
      • ProductCategoryRow
      • ProductRow

🛠️ Section 3: Step 2 - Build a Static Version

Now, let's build a version that renders the UI from our data model without any interactivity. We'll pass the data down from the top using props.

ProductRow.jsx

This component is simple. It just receives a product object and displays its name and price.

function ProductRow({ product }) {
const name = product.stocked ? product.name :
<span style={{ color: 'red' }}>
{product.name}
</span>;

return (
<tr>
<td>{name}</td>
<td>{product.price}</td>
</tr>
);
}

ProductTable.jsx

This component will take the list of all products and render the rows, adding category headers where needed.

function ProductTable({ products }) {
const rows = [];
let lastCategory = null;

products.forEach((product) => {
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category} />
);
}
rows.push(
<ProductRow
product={product}
key={product.name} />
);
lastCategory = product.category;
});

return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}

(Note: ProductCategoryRow would be a simple component that renders a <tr> with a <th> spanning two columns).

FilterableProductTable.jsx

This is our top-level component. For now, it just renders the ProductTable and passes the initial data down.

function FilterableProductTable({ products }) {
return (
<div>
{/* SearchBar will go here */}
<ProductTable products={products} />
</div>
);
}

At this point, we have a static application that correctly renders our product data. We've successfully passed our data down through the component hierarchy using props.


🤔 Section 4: Step 3 - Identify the Minimal Representation of State

Now for the crucial thinking part. We need to identify every piece of data in our application that can change over time. This is our state.

Ask yourself: what are the variables that can be changed by user input?

  1. The search text in the input field.
  2. The value of the "in stock" checkbox.

That's it. The original list of products is passed in as props, so it's not state. The filtered list of products can always be computed from the original list, the search text, and the checkbox value, so it's not state either.

Remember the DRY principle: Don't Repeat Yourself. We want the minimal representation of state. Anything else can be computed on the fly.

Our application state is:

  • searchText: a string.
  • inStockOnly: a boolean.

Key Takeaways

  • Component Hierarchy is King: Starting with a clear component hierarchy makes development much more organized.
  • Static First, Interactive Later: Building a static version lets you focus on getting the data flow right before worrying about state changes.
  • State is Minimal: We've identified the absolute minimum pieces of data that need to be managed as state, which will make our application much easier to reason about.
  • DRY Principle Applies to State: Never store derived data as state. If something can be calculated from props or other state, compute it on the fly.
  • Props Flow Down, Events Flow Up: Data flows from parent to child via props, and user interactions flow back up through callbacks.

Frequently Asked Questions

How do I know when to split a UI into separate components?

Break a UI into components when a piece of the interface has a single responsibility, appears multiple times, or is complex enough to warrant its own file. If you find yourself copying and pasting similar JSX, that's a sign to extract a component. A good rule of thumb is that if you can describe a component's purpose in a simple sentence, it's probably the right size.

Should I pass all my state to the root component?

Not necessarily. State should live in the lowest common ancestor of all components that need it. If only two child components need a piece of state, don't lift it to the root. This keeps your component tree clean and makes it easier to reason about data flow.

What is the DRY principle in the context of React state?

DRY (Don't Repeat Yourself) means you should never store the same data in multiple places or store data that can be computed from other data. For example, don't store both a firstName and fullName in state if you can derive fullName from firstName and lastName.

How do I decide what data should be state versus props?

Ask yourself: can this data be passed down from a parent component? If yes, it should be a prop. Does this data change within the component? If yes, it should be state. Data that doesn't change is usually derived or constant.

Can I refactor my component hierarchy later?

Yes, React's component-based architecture makes refactoring relatively straightforward. You can extract components, reorganize your hierarchy, and move state around as your understanding of the application improves. This is one of React's greatest strengths.


Further Reading


Glossary

  • Component Hierarchy: The tree-like structure of React components, showing parent-child relationships and how data flows between them.
  • State: Data in a React component that can change over time and trigger re-renders when updated.
  • Props: Data passed from a parent component to a child component, which cannot be modified by the child.
  • Single Responsibility Principle: A design principle stating that a component should do one thing well rather than trying to handle multiple concerns.
  • DRY (Don't Repeat Yourself): A principle that encourages reducing code duplication and avoiding storing the same data in multiple places.

➡️ Next Steps

Now that we know what our state is, the next logical question is where should it live? In the next article, "Thinking in React: A Practical Example (Part 2)", we will tackle the final steps: identifying the owner of our state and implementing inverse data flow to make our application fully interactive.

The most challenging part is over. Let's get ready to make it work.