Skip to main content

Thinking in React: A Practical Example (Part 2) #64

📖 Introduction

Welcome to the conclusion of our two-part series on "Thinking in React." In Part 1, we laid the essential groundwork for building a React application by breaking down a UI into a component hierarchy, creating a static version, and identifying the minimal representation of state.

Now, we'll bring our application to life. This article will focus on the final, dynamic steps of the process: deciding where our state should live and wiring up the components to communicate with each other. Let's complete our journey from architect to builder.


📚 Prerequisites

This article builds directly on Part 1. Please ensure you are familiar with:

  • The component hierarchy we designed for our searchable product table.
  • The concept of minimal state, specifically our identified state: searchText and inStockOnly.
  • The pattern of passing callbacks to enable inverse data flow.

🎯 Article Outline: What You'll Master

In this article, we will complete our searchable product table by implementing the final two steps of the "Thinking in React" methodology:

  • Step 4: Locate the State Owner: A clear strategy for identifying the single, correct component to own and manage the state.
  • Step 5: Implement Inverse Data Flow: Passing callback functions down to child components to allow them to update the parent's state.
  • The Grand Finale: Assembling the fully interactive, stateful application.

🤔 Section 1: Step 4 - Identify Where Your State Should Live

Let's recap. We have our component hierarchy:

  • FilterableProductTable
    • SearchBar
    • ProductTable

And we have our minimal state:

  • searchText
  • inStockOnly

Now, we must decide which component will be the "owner" of this state. For each piece of state, we need to:

  1. Identify every component that renders something based on that state.
    • ProductTable needs to filter the list based on searchText and inStockOnly.
    • SearchBar needs to display the current searchText and inStockOnly values in the form fields.
  2. Find their closest common ancestor.
    • The common parent of ProductTable and SearchBar is FilterableProductTable.

The conclusion is clear: FilterableProductTable is the correct owner for our state. It's the single source of truth.


💻 Section 2: Step 5 - Add Inverse Data Flow

Now that we know where our state lives, we can make our app interactive. We'll use the useState hook in FilterableProductTable and then pass the state and callback functions down to the SearchBar.

2.1 - Updating the FilterableProductTable

Let's add the state and the callback functions to our top-level component.

// FilterableProductTable.jsx

import React, { useState } from 'react';
// Assume other components are imported

function FilterableProductTable({ products }) {
const [searchText, setSearchText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);

return (
<div>
<SearchBar
searchText={searchText}
inStockOnly={inStockOnly}
onSearchTextChange={setSearchText}
onInStockOnlyChange={setInStockOnly}
/>
<ProductTable
products={products}
searchText={searchText}
inStockOnly={inStockOnly}
/>
</div>
);
}

Code Breakdown:

  • We initialize our two state variables using useState.
  • We pass the state values (searchText, inStockOnly) down to both SearchBar and ProductTable.
  • Crucially, we also pass the state setter functions (setSearchText, setInStockOnly) down to the SearchBar. This is our inverse data flow.

The SearchBar will now be a controlled component. It receives its values from its parent and tells the parent when to update them.

// SearchBar.jsx

function SearchBar({
searchText,
inStockOnly,
onSearchTextChange,
onInStockOnlyChange
}) {
return (
<form>
<input
type="text"
placeholder="Search..."
value={searchText}
onChange={(e) => onSearchTextChange(e.target.value)}
/>
<p>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}
/>
{' '}
Only show products in stock
</p>
</form>
);
}

Code Breakdown:

  • The input's value is now controlled by the searchText prop.
  • The checkbox's checked status is controlled by the inStockOnly prop.
  • When the user types in the search bar, the onChange event calls the onSearchTextChange callback (which is setSearchText in the parent), passing the new text up.
  • When the user clicks the checkbox, the onChange event calls onInStockOnlyChange (which is setInStockOnly in the parent), passing the new boolean value up.

🛠️ Section 3: The Grand Finale: Filtering the Data

The final piece of the puzzle is to actually use the state to filter the data. We'll do this in the ProductTable component.

// ProductTable.jsx

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

products.forEach((product) => {
// Filter logic starts here
if (product.name.toLowerCase().indexOf(searchText.toLowerCase()) === -1) {
return;
}
if (inStockOnly && !product.stocked) {
return;
}
// Filter logic ends here

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>
{/* ... table structure ... */}
<tbody>{rows}</tbody>
</table>
);
}

And with that, our application is complete and fully interactive!


💡 Conclusion & Key Takeaways

This two-part series has walked you through the five steps of "Thinking in React." By following this methodical process, you can break down any complex UI into a simple, predictable, and maintainable application.

Let's summarize the entire process:

  1. Break UI into a component hierarchy: We drew boxes around our UI to define our components.
  2. Build a static version: We rendered the UI with props, without interactivity.
  3. Identify minimal state: We found the two core pieces of state that change over time.
  4. Identify state's owner: We found the common ancestor (FilterableProductTable) to own the state.
  5. Add inverse data flow: We passed state setters down to child components to allow them to update the state.

Mastering this process is the key to unlocking your potential as a React developer. It transforms React from a library of functions into a powerful new way of thinking about user interfaces.


➡️ Next Steps

Congratulations on completing Chapter 2! You have now mastered the core concepts of interactivity and state management in React. You understand how to handle events, manage state with useState, and architect applications with a clear, unidirectional data flow.

The journey is far from over. In the next chapter, "Advanced React Concepts," we will explore even more powerful features, starting with the useEffect hook, which will allow us to introduce side effects like data fetching into our applications.

Your foundation is solid. Now, let's build upon it.