Updated June 2026. Tested on React 19 and modern JavaScript. Part of the Techalyst React series.

Components are only useful if they can talk to each other and slot together. React has a small, consistent set of tools for this: props send data down, callbacks send events up, and composition lets a component wrap whatever UI you give it. Knowing all of them is what stops you from building rigid components that need a new boolean prop for every variation.

Props send data down

A parent passes data to a child through props. The child receives them as its argument and renders from them:

function UserCard({ name, role }) {
  return <div>{name} · {role}</div>;
}

<UserCard name="Akram" role="Engineer" />

Props are read-only. A child never changes its own props, it just renders what it is given. When the parent passes new values, the child re-renders with them.

Callbacks send events up

Data flows down, but children need a way to tell the parent something happened. They cannot reach up, so the parent passes a function down, and the child calls it:

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

<SearchBar onSearch={handleSearch} />

This is the core pattern: data down through props, events up through callbacks. The parent owns the state and the logic, the child just reports back. Lifting shared state into the common parent and passing callbacks down is how unrelated children stay in sync.

children: passing UI in

Props pass data, but you can also pass entire chunks of UI. Whatever sits between a component's tags arrives as the special children prop, which is React's version of a slot:

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

<Card>
  <h2>Title</h2>
  <p>Some body text.</p>
</Card>

Card does not know or care what is inside it, it just wraps it. This is how you build reusable shells, modals, panels, layouts, that hold arbitrary content.

More than one slot

When you need several slots, just pass elements as named props. There is no special syntax, an element is a perfectly good prop value:

function Layout({ header, sidebar, children }) {
  return (
    <div>
      <header>{header}</header>
      <aside>{sidebar}</aside>
      <main>{children}</main>
    </div>
  );
}

<Layout header={<Header />} sidebar={<Sidebar />}>
  <Content />
</Layout>

Render props

Sometimes a component holds some logic but should let the caller decide how to render it. You pass a function as the child, and the component calls it with data:

function MousePosition({ children }) {
  const [pos, setPos] = useState({ x: 0, y: 0 });
  // ...track mouse into pos...
  return <div onMouseMove={...}>{children(pos)}</div>;
}

<MousePosition>
  {({ x, y }) => <p>{x}, {y}</p>}
</MousePosition>

Render props were the main way to share behaviour before hooks, and custom hooks have taken over most of that job. But the pattern is still the right tool when a component owns rendering-related state and wants to hand it to the caller to display.

Composition over configuration

The thread through all of this: prefer composing components over piling configuration onto one. When you feel a component sprouting a tenth boolean prop to cover another variation, that is usually the signal to let it accept children and compose the variation from the outside instead. Flexible components are built by composition, not by options.

Wrapping up

React components communicate with a tidy set of tools: props pass data down, callbacks pass events up, and children passes UI in, with extra named-element props for multiple slots and render props for when a component owns state but not its presentation. Lean on composition over a growing list of configuration props, and your components stay reusable instead of turning into a tangle of flags.