Skip to main content

Thinking in React: A Practical Example (Part 1) #63

📖 Introduction

So far, 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.

💡 Conclusion & Key Takeaways

We've laid a powerful foundation for our application. By methodically breaking down the UI and identifying the core state, we've avoided a lot of potential complexity.

Let's review our progress:

  • 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.

The groundwork is complete. You've designed the blueprint for the application and identified the moving parts. In the next article, we'll take this foundation and bring it to life.


➡️ 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.