Skip to content

React Tutorial Advanced React Concepts

Sharing State Between Components: Props, Context and More

Lesson 114 of 156 3 min read By Deepak Paswan Updated Oct 10, 2026

Runnable lesson · press Try it on the examples to edit and run them here.

You have now seen several ways for components to share data. Which one should you reach for? The answer depends on how far the data has to travel and how often it changes.

The options, in order of simplicity

TechniqueUse whenCost
PropsThe data goes to a direct child or a few levels downNothing special
Lifting state upSiblings need the same dataThe parent re-renders when it changes
Composition (children, slots)Many layers just pass data throughA bit of restructuring
ContextMany components at different depths need it, and it changes rarelyAll consumers re-render when the value changes
A state library or external storeLots of shared client state, complex updates, very frequent changesAnother dependency to learn
The URLThe state should be shareable and survive refreshParsing and updating the URL
Server-state librariesThe data comes from an APISee Data-Fetching Libraries

Always try the simplest one first.

Prop drilling, and why it is not always bad

Prop drilling means passing a prop through components that do not use it, just to reach a deep child. Two or three levels is perfectly normal and makes the data flow easy to follow. It becomes a problem when the same data is threaded through many layers.

Composition: avoid drilling without context

Often the middle layers do not need to know the data at all. Instead of passing user down through Layout and Sidebar, create the element where the data is and pass it down:

App.jsx
JSX
// Before: App -> Layout(user) -> Sidebar(user) -> Avatar(user): everyone passes "user"
// After: App builds <Avatar user={user} /> and hands it to Layout as a prop.

function Avatar({ user }) {
  return <strong>Hello, {user}</strong>;
}

function Sidebar({ avatar }) {
  return <aside>{avatar}</aside>;     // Sidebar does not know about "user"
}

function Layout({ sidebar, children }) {
  return (
    <div style={{ display: "grid", gridTemplateColumns: "140px 1fr", gap: 12 }}>
      {sidebar}
      <main>{children}</main>
    </div>
  );
}

function App() {
  const [user, setUser] = React.useState("Ada");
  return (
    <Layout sidebar={<Sidebar avatar={<Avatar user={user} />} />}>
      <button onClick={() => setUser(user === "Ada" ? "Grace" : "Ada")}>Switch user</button>
    </Layout>
  );
}

Layout and Sidebar know nothing about users. The data is passed only where it is used. This is often the cleanest fix, and it is covered in Component Composition.

Context: data for a whole subtree

When many components at many depths need the same value (theme, locale, signed-in user), Context avoids threading it through everyone. The next lesson, Context API, shows how to build it properly.

Remember two limits of Context:

  • It is for values read by many components, not a general “global variable” replacement.
  • Every consumer re-renders when the value changes, so it is poor for fast-changing data.

External stores

For large apps with plenty of client-side state (a drawing canvas, a multi-step editor, a shopping cart with many actions), libraries such as Redux Toolkit, Zustand and Jotai keep state outside React components and let components subscribe to just the parts they use. They are worth learning when useReducer plus Context becomes awkward. React gives them a hook, useSyncExternalStore, for safe subscriptions.

The URL is state too

Filters, sort order, page numbers and the selected tab belong in the URL when users might share or bookmark the view. See Route Parameters.

A decision guide

  1. Is the state used by one component? Keep it there (useState).
  2. By a parent and children? Pass props.
  3. By siblings? Lift it up.
  4. Is it passed through many layers that ignore it? Try composition first, then Context.
  5. Is it server data? Use a fetching approach or library, not global state.
  6. Should it survive refresh or be shared? Put it in the URL, storage or on the server.
  7. Is it big, shared and frequently updated? Consider a store library.

Do not duplicate state

Whatever you choose, keep one source of truth. Copying the same data into several components’ state is the root of “my screens disagree” bugs. See Derived State.

Key points

  • Start with props and lifting state up; try composition before context.
  • Use Context for widely needed, slowly changing values; a store for large shared client state.
  • Keep one source of truth, and consider the URL for shareable state.