DEV Community

Cover image for Thinking in React: A Step‑by‑Step Approach
Md Enayetur Rahman
Md Enayetur Rahman

Posted on

Thinking in React: A Step‑by‑Step Approach

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
Enter fullscreen mode Exit fullscreen mode

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} &hearts;</td>
    </tr>
  );
}
Enter fullscreen mode Exit fullscreen mode

4 – Identify the Minimal & Mutable State

Walk through every piece of data in your app and ask:

  1. Does another component compute it?
  2. Does it remain unchanged?
  3. 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>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why it works

  • Single source of truthfilterText lives in TweetSearchResults, the nearest common ancestor.
  • Pure childrenTweetList, TweetRow, and TweetCategory 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)