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:
searchTextandinStockOnly. - 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:
FilterableProductTableSearchBarProductTable
And we have our minimal state:
searchTextinStockOnly
Now, we must decide which component will be the "owner" of this state. For each piece of state, we need to:
- Identify every component that renders something based on that state.
ProductTableneeds to filter the list based onsearchTextandinStockOnly.SearchBarneeds to display the currentsearchTextandinStockOnlyvalues in the form fields.
- Find their closest common ancestor.
- The common parent of
ProductTableandSearchBarisFilterableProductTable.
- The common parent of
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 bothSearchBarandProductTable. - Crucially, we also pass the state setter functions (
setSearchText,setInStockOnly) down to theSearchBar. This is our inverse data flow.
2.2 - Updating the SearchBar
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'svalueis now controlled by thesearchTextprop. - The
checkbox'scheckedstatus is controlled by theinStockOnlyprop. - When the user types in the search bar, the
onChangeevent calls theonSearchTextChangecallback (which issetSearchTextin the parent), passing the new text up. - When the user clicks the checkbox, the
onChangeevent callsonInStockOnlyChange(which issetInStockOnlyin 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:
- Break UI into a component hierarchy: We drew boxes around our UI to define our components.
- Build a static version: We rendered the UI with props, without interactivity.
- Identify minimal state: We found the two core pieces of state that change over time.
- Identify state's owner: We found the common ancestor (
FilterableProductTable) to own the state. - 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.