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.
08On this page
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?
- Find every component that reads the value.
- Find their closest common parent.
- 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
| Approach | Good for | Cost |
|---|---|---|
| Plain CSS / CSS Modules | Almost everything. Modules scope class names per file | You name things yourself |
| Tailwind | Speed, and a design system by default | Long class strings; a build step |
| CSS-in-JS | Styles that depend on props at runtime | Runtime cost; awkward with Server Components |
Inline style | One or two computed values | No 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."
Opens in the editor: write it, run it, and check it against real tests.
All 46 for this level →Finished reading?
0 of 57 chapters read in this series