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
andinStockOnly
. - 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:
- Identify every component that renders something based on that state.
ProductTable
needs to filter the list based onsearchText
andinStockOnly
.SearchBar
needs to display the currentsearchText
andinStockOnly
values in the form fields.
- Find their closest common ancestor.
- The common parent of
ProductTable
andSearchBar
isFilterableProductTable
.
- 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 bothSearchBar
andProductTable
. - 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
'svalue
is now controlled by thesearchText
prop. - The
checkbox
'schecked
status is controlled by theinStockOnly
prop. - When the user types in the search bar, the
onChange
event calls theonSearchTextChange
callback (which issetSearchText
in the parent), passing the new text up. - When the user clicks the checkbox, the
onChange
event callsonInStockOnlyChange
(which issetInStockOnly
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:
- 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.