Skip to content
WANCODE
BM

Component Composition

22 minIntermediateReact

In this lesson

  • Split components into their own files and import them
  • Use the children prop to build wrapper components
  • Lift state up to the closest common parent and pass data down / callbacks up

The big idea

Big apps are small components fitted together. Data flows down through props, and children talk up by calling functions their parent gave them. When two components need the same data, move it up to their shared parent.

Real projects keep each component in its own file. Export it as the default, then import it wherever you need it. Open Panel.jsx and ProductRow.jsx in the editor — they're imported at the top of App.jsx.

App.jsx
import Panel from "./Panel.jsx";
import ProductRow from "./ProductRow.jsx";

children: components that wrap things

Whatever you put between a component's opening and closing tags arrives as a special prop called children. That lets you build frames, cards, modals and layouts that don't care what goes inside.

JSX
function Card({ title, children }) {
  return (
    <div className="card">
      <h2>{title}</h2>
      {children}
    </div>
  );
}

<Card title="Today's special">
  <p>Nasi kerabu with ayam percik</p>
  <button>Order</button>
</Card>

Lifting state up

In the starter, each ProductRow keeps its own qty. That works — until the order panel needs the total. A parent can't read its children's state. The fix is to lift the state up:

  1. Find the closest parent of everything that needs the data (here: App).
  2. Move the state there.
  3. Pass the value down as a prop (qty) and pass callbacks down (onAdd, onRemove) that the child calls to ask for a change.

State trapped in each row

JSX
function ProductRow({ product }) {
  const [qty, setQty] = useState(0);
  return <button onClick={() => setQty(qty + 1)}>+</button>;
}

Parent owns it; row reports clicks

JSX
function ProductRow({ product, qty, onAdd }) {
  return <button onClick={onAdd}>+</button>;
}

// in App:
<ProductRow qty={cart[p.id] ?? 0} onAdd={() => changeQty(p.id, 1)} />

Data down, events up

A child never changes its parent's state directly. It calls a function prop — by convention named onSomething — and the parent decides what to do. One owner, one source of truth.

Once the state lives in App, things that were impossible become easy: a running total, a "Clear order" button that resets every row at once, saving the cart… all from one place.

The two panels show their titles but nothing inside. In Panel.jsx, accept the children prop and render it below the <h2>. Both the fruit list and the order total should appear inside their panels.

Checks

  • Two panels with titles
  • The fruit rows appear inside a panel
  • The total appears inside a panel

Check understanding

Q1

In <Card title="Hi"><p>Body</p></Card>, how does Card receive the <p>?

Q2

Two sibling components need the same piece of state. Where should it live?

Q3

How should a child ask its parent to change the parent's state?

What you learned

  • Put components in their own files with export default and import X from "./X.jsx".
  • children lets a component wrap whatever is placed inside it.
  • Shared state goes in the closest common parent: values flow down as props, changes flow up through callbacks.
Preview

Console output from your code appears here.

Step 1 of 3