Learning Patterns #16
React doesn’t just give you an API—it encourages a mindset. In “Thinking in React” Lydia Hallie & Addy Osmani outline a clear, repeatable workflow that scales from prototypes to production. Below is a distilled guide with fresh, hook‑based examples you can apply right away.
1 – Start with a Mock
Before writing code, sketch the final UI (Excalidraw is great for this). Note the real data your API will provide and how it should appear. Having a concrete mock prevents “blank‑canvas paralysis” and reveals edge cases early.
2 – Break the UI into a Component Hierarchy
Draw boxes around every logical piece of the mock and label them. Follow the Single‑Responsibility Principle: one component, one job. Small, focused components compose better than monoliths.
TweetSearchResults
├── SearchBar
└── TweetList
├── TweetCategory
└── TweetRow
3 – Build a Static Version
Implement the hierarchy using React (hooks make this even faster). Keep it stateless for now—hard‑code the data and just get pixels on the screen.
function TweetRow({ tweet }) {
return (
<tr>
<td>{tweet.author}</td>
<td>{tweet.text}</td>
<td>{tweet.likes} ♥</td>
</tr>
);
}
4 – Identify the Minimal & Mutable State
Walk through every piece of data in your app and ask:
- Does another component compute it?
- Does it remain unchanged?
- Can you derive it from existing props or state?
If the answer is yes to any, it isn’t state. What’s left is the minimal state—keep it close to where it’s needed.
5 – Add Inverse Data Flow
State often lives higher in the tree than the components that need it. Pass state down via props and communicate changes upward with callbacks (i.e., “lifting state”). This preserves React’s one‑way data flow while keeping child components pure.
Example – Filterable Tweet Table
import { useState } from "react";
const TWEETS = [
{ id: 1, category: "Cats", author: "Ada", text: "Cat naps > cat apps", likes: 7 },
{ id: 2, category: "Dogs", author: "Ben", text: "Who wants fetch?", likes: 3 },
{ id: 3, category: "Cats", author: "Ada", text: "Scratching post tips", likes: 4 },
];
function SearchBar({ filterText, onFilterTextChange }) {
return (
<input
placeholder="Search tweets…"
value={filterText}
onChange={(e) => onFilterTextChange(e.target.value)}
className="p-2 border rounded w-full mb-4"
/>
);
}
function TweetCategory({ category }) {
return (
<tr>
<th colSpan={3} className="text-left bg-gray-100">{category}</th>
</tr>
);
}
function TweetRow({ tweet }) {
return (
<tr>
<td className="pr-4">{tweet.author}</td>
<td className="flex-1">{tweet.text}</td>
<td>{tweet.likes}</td>
</tr>
);
}
function TweetList({ tweets, filterText }) {
const rows = [];
let lastCategory = null;
tweets.forEach((t) => {
if (!t.text.toLowerCase().includes(filterText.toLowerCase())) {
return;
}
if (t.category !== lastCategory) {
rows.push(<TweetCategory key={t.category} category={t.category} />);
lastCategory = t.category;
}
rows.push(<TweetRow key={t.id} tweet={t} />);
});
return <tbody>{rows}</tbody>;
}
export default function TweetSearchResults() {
const [filterText, setFilterText] = useState("");
return (
<div className="max-w-xl mx-auto">
<SearchBar
filterText={filterText}
onFilterTextChange={setFilterText}
/>
<table className="w-full border">
<TweetList tweets={TWEETS} filterText={filterText} />
</table>
</div>
);
}
Why it works
-
Single source of truth –
filterText
lives inTweetSearchResults
, the nearest common ancestor. -
Pure children –
TweetList
,TweetRow
, andTweetCategory
render solely from props. -
Declarative updates – Typing in
SearchBar
updates state, React handles the DOM.
Takeaways
- Sketch first, code second.
- Components are units of responsibility—keep them small and focused.
- Derive, don’t duplicate. Store the least possible state.
- Lift state for predictable, one‑way data flow.
Happy building!
Adapted from *“Learning Patterns – Thinking in React”** by Lydia Hallie & Addy Osmani.*
Top comments (0)