Skip to the chapter
←ReactGGroundwork⚛React
React/Beginner/B10
10 / 57 written chapters
/

The cover
  1. B1Setup & JSX
  2. B2Thinking in React
  3. B3Components
  4. B4Props
  5. B5useState
  6. B6Events & conditionals
  7. B7Lists & keys
  8. B8Forms
  9. B9useEffect
  10. B10Lifting state & styling
  11. B11Guided project
  12. ★Cheat page
  1. I1useRef
  2. I2Context
  3. I3useReducer
  4. I4Custom hooks
  5. I5Effects in depth
  6. I6Routing
  7. I7State architecture
  8. I8Memoisation
  9. I9Boundaries & portals
  10. I10Data fetching
  11. I11Forms at scale
  12. I12State libraries
  13. I13Auth
  14. I14TanStack Query
  15. I15Testing
  16. I16TypeScript
  17. I17Animation
  18. I18E2E testing
  19. I19i18n
  20. I20File uploads
  21. I21Real-time UI
  22. I22shadcn/ui
  23. ★Cheat page
  1. A1Fiber
  2. A2Concurrent
  3. A3Suspense
  4. A4Server Components
  5. A5Server Actions
  6. A6use()
  7. A7Rendering strategies
  8. A8Effect timing
  9. A9External stores
  10. A10Refs in depth
  11. A11Component patterns
  12. A12Performance
  13. A13Accessibility
  14. A14Design systems
  15. A15Advanced TypeScript
  16. A16Architecture at scale
  17. A17Custom renderers
  18. A18Migrating & upgrading
  19. A19Production debugging
  20. A20Review & judgement
  21. A21State machines
  22. A22Feature flags
  23. A23Interview bank
  24. ★Cheat page

⚛B10 · Part 1 · Beginner

Lifting state up & basic styling

When two components need the same value, it belongs to the one above both of them.

5 min read8 sections2 exercises
10 of 57

In this chapter

  • The problem
  • Where should this state live?
  • Prop drilling, and when it is actually a problem
  • Composition beats drilling
  • Colocation: keep state as low as it will go

and 3 more

about 5 min
08On this page
  1. The problem
  2. Where should this state live?
  3. Prop drilling, and when it is actually a problem
  4. Composition beats drilling
  5. Colocation: keep state as low as it will go
  6. Styling: the four options
  7. Design tokens beat scattered values
  8. Putting the tier together

The problem

Two siblings need the same piece of state — a search box and a list, a filter and a table. They cannot see each other; React data only flows down. So the value moves up to the closest component that contains both, and comes back down as props.

function Page() {
  const [query, setQuery] = useState("");        // owned here

  return (
    <>
      <SearchBox value={query} onChange={setQuery} />
      <Results query={query} />
    </>
  );
}

function SearchBox({ value, onChange }) {
  return <input value={value} onChange={(e) => onChange(e.target.value)} />;
}

SearchBox now has no state of its own. It renders what it is given and reports what happened — which also makes it trivial to test and reusable anywhere.

Controlled and uncontrolled, for your own components

A component holding its own state is uncontrolled: easy to drop in, but the parent cannot read or set it. A component taking value and onChange is controlled: more wiring, complete control. The same distinction as a DOM input, and the same trade.

Where should this state live?

  1. Find every component that reads the value.
  2. Find their closest common parent.
  3. Put the state there. Pass the value down, pass a setter down.

Then stop. The instinct to put everything at the top of the app is the wrong one — state at the root re-renders the whole tree and makes every component below it dependent on a page it should not know about.

Prop drilling, and when it is actually a problem

Passing a prop through two or three layers is fine. It is explicit and you can trace it. It becomes a problem when a value crosses many layers that have no use for it — a theme, the signed-in user, a locale.

Two answers before you reach for a library: compose instead, by passing the finished element down as children so it never needs the prop in between; and context, for values that are genuinely ambient. Both are covered later; the mistake is jumping to a global store on the third prop.

Composition beats drilling

Before adding context, check whether the prop needs to travel at all. If the middle layers only pass it through, hand them the finished element instead.

// ✗ Layout and Sidebar both take a prop neither uses
<Layout user={user}>
  <Sidebar user={user}>
    <Profile user={user} />

// ✓ built at the top, passed as an element — nothing in between knows
<Layout sidebar={<Sidebar><Profile user={user} /></Sidebar>} />

The element is created where the data lives, so the layers it travels through carry it as opaque children. This removes most prop drilling people reach for a store to solve.

Colocation: keep state as low as it will go

The opposite of lifting, and just as important. A modal's open/closed flag belongs to the component that owns the modal, not to the page. State at the top of the tree re-renders everything below it and couples components that should not know about each other.

Lift when two components genuinely need the same value. Push it back down the moment only one does.

Styling: the four options

ApproachGood forCost
Plain CSS / CSS ModulesAlmost everything. Modules scope class names per fileYou name things yourself
TailwindSpeed, and a design system by defaultLong class strings; a build step
CSS-in-JSStyles that depend on props at runtimeRuntime cost; awkward with Server Components
Inline styleOne or two computed valuesNo pseudo-classes, no media queries, no cascade

CSS Modules

// Card.module.css
.card { border: 1px solid #ddd; }
.title { font-weight: 700; }

// Card.jsx
import styles from "./Card.module.css";

<div className={styles.card}>
  <h2 className={styles.title}>{title}</h2>
</div>

The build renames .card to something unique per file, so two components can both have a .card without colliding. No naming convention to enforce, no cascade surprises.

Conditional classes

<button className={"btn " + (active ? "btn--active" : "")}>

<button className={["btn", active && "btn--active", size && "btn--" + size]
  .filter(Boolean)
  .join(" ")}>

Past two or three conditions, the clsx package does exactly this in one call and is worth the dependency.

Inline styles, and their limits

<div style={{ width: percent + "%", backgroundColor: color }} />

Right for a value computed at runtime, like a progress bar's width. Wrong as a general approach: no :hover, no @media, no ::before, and a new object every render.

A rule that keeps components reusable

A component should not set its own margin. Margin is about the relationship between a component and its neighbours, and only the parent knows that. Let the layout own spacing — a gap on the container — and the component can be dropped anywhere without fighting it.

Design tokens beat scattered values

:root {
  --ink: #1f3a73;
  --paper: #fffdf6;
  --radius: 12px;
}
@media (prefers-color-scheme: dark) {
  :root { --ink: #d9e5fb; --paper: #191d25; }
}

.card { background: var(--paper); color: var(--ink); border-radius: var(--radius); }

Custom properties cascade and can be swapped at runtime, so dark mode becomes a handful of redefinitions rather than a second stylesheet. Components read tokens and never literals — then a colour changes in one place, not forty.

This is worth setting up on day one of a project. Retrofitting tokens onto a codebase full of hex codes is a long afternoon.

Putting the tier together

With these ten chapters you can build a real screen: components composed from props, state where it belongs, events and conditions, lists with correct keys, a controlled form, and an effect for the one thing that genuinely needs to reach outside React.

The next chapter is where you do exactly that, end to end — a task board, built in the order a machine-coding round expects.

What you still cannot do is explain why it re-rendered. That is the intermediate tier — refs, context, memoisation, and the fetching patterns that do not race themselves — and it starts with useRef.

Interview answer, one sentence

"When two components need the same state, lift it to their closest common parent and pass it down with callbacks to change it, keeping a single source of truth; style with plain CSS, modules or utility classes, and reserve inline style objects for values that genuinely change at runtime."

02Practice this chapter

Opens in the editor: write it, run it, and check it against real tests.

  • 9 testsTwo inputs, one source of truthbeginner
  • 5 testsA shared selection, lifted above both panelsbeginner
All 46 for this level →
B10

Finished reading?

0 of 57 chapters read in this series

→Next stop

  1. B9Previous · B9useEffect basics
  2. B10you are here
  3. Next · B11Guided project: a task boardEvery beginner chapter, in one app built end to end.4 min read

01On this page

8 sections · 0%

  1. 01The problem
  2. 02Where should this state live?
  3. 03Prop drilling, and when it is actually a problem
  4. 04Composition beats drilling
  5. 05Colocation: keep state as low as it will go
  6. 06Styling: the four options
  7. 07Design tokens beat scattered values
  8. 08Putting the tier together

[] chapters · t top