Lifting State up in React

Hero image for Lifting State up in React. Image by Simone Hutsch.
Hero image for 'Lifting State up in React.' Image by Simone Hutsch.

This Article is Over Ten Years Old...

Things can and do move very quickly in tech, which means that tech-related articles go out of date almost as soon as they have been written and published. If you are looking for up-to-date technical advice or opinion, it is unlikely that you will find it on this page.

You may find that my recent articles are more relevant, and you are always welcome to drop me a line if you have a specific technical problem you are trying to solve.

One of the first React design problems that feels slightly bigger than syntax is this: two components both need to reflect the same piece of changing data.

At first, the easiest move seems to be giving each component its own local state. That works for a moment, and then the UI starts drifting out of sync. One part of the interface thinks the filter is "sale", another thinks it is "all", and now we are debugging state that should never have been duplicated in the first place.

This is where lifting state up becomes important.

The idea is simple enough:

  • move shared state to the nearest common parent
  • pass the current value down as props
  • pass callbacks down so children can request updates

The result is a single source of truth instead of several competing copies.


The Problem Usually Starts with Duplicated Local State

Imagine a small product page with two related children:

  • a search input
  • a results summary

If both components try to own the query state separately, trouble follows:

const SearchInput = (): JSX.Element => {  const [query, setQuery] = useState('');  return (    <input      value={query}      onChange={(event) => setQuery(event.target.value)}    />  );};const ResultsSummary = (): JSX.Element => {  const [query] = useState('');  return <p>Showing results for "{query}"</p>;};

These two components may look related, but they are not actually sharing anything. They each have their own state. Updating one does not update the other.

That is the clue that the state lives in the wrong place.


Shared State Should Usually Live in the Nearest Common Parent

If both children depend on the same query, the parent should own it:

const SearchPanel = (): JSX.Element => {  const [query, setQuery] = useState('');  return (    <>      <SearchInput query={query} onQueryChange={setQuery} />      <ResultsSummary query={query} />    </>  );};

Now the parent owns the state, and each child receives exactly what it needs.

That is lifting state up in practice. We are not making the application more complicated. We are putting the state where its real responsibility already was.


Data Goes Down, Events Come Back up

This is the rhythm worth remembering.

The parent passes data down:

type SearchInputProps = {  query: string;  onQueryChange: (value: string) => void;};const SearchInput = ({  query,  onQueryChange,}: SearchInputProps): JSX.Element => {  return (    <input      value={query}      onChange={(event) => onQueryChange(event.target.value)}    />  );};

The child does not own the query any more. It renders the value it receives and tells the parent when the user wants it changed.

That separation is one of the reasons React code can stay fairly predictable. The component displaying the input and the component owning the data do not have to be the same thing.


A Single Source of Truth Prevents Drift

This phrase gets repeated so often because it matters.

Once state is duplicated, the question becomes: which copy is the real one?

That question is expensive because it creates edge cases:

  • one child updates, another does not
  • one copy resets while another persists
  • validation logic only runs in one place
  • the UI shows conflicting information

Lifting state up avoids that class of bug by making it impossible for siblings to silently disagree about the same value.


React's own documentation has long used temperature conversion as a teaching example, and it is still a good one because it shows the principle cleanly.

If one input is in Celsius and another in Fahrenheit, both represent the same underlying temperature. If each input owns its own local state, they drift apart. If the parent owns the canonical temperature and passes converted values down, the interface stays coherent.

The specific example matters less than the pattern:

  • if several components represent the same underlying fact, they should not each invent their own state for it

Lifting State up is Not the Same as Pushing Everything to the Top

This is the mistake people make after first learning the pattern.

Not every piece of state should be moved upwards.

Local state is still a good idea when it is genuinely local:

  • whether one dropdown is open
  • the text currently typed into a selfcontained widget
  • whether a tooltip is visible

State should move higher only when more than one part of the tree genuinely depends on it.

If the state only matters to one component, leaving it there is usually the better design.


The Phrase "Nearest Common Parent" is Doing Important Work

Suppose two siblings need the same state. Move it to their parent.

Suppose three cousins in different branches need it. Move it to the nearest ancestor that covers all three.

Do not automatically jump to the top of the app. That only replaces one problem with another by creating props that travel through components which do not care about them.

Good lifting is precise, not dramatic.


This Also Makes Derived State Easier to Reason About

Once the shared state lives in one place, derived values become easier to calculate centrally:

const SearchPanel = (): JSX.Element => {  const [query, setQuery] = useState('');  const trimmedQuery = query.trim();  const hasQuery = trimmedQuery.length > 0;  return (    <>      <SearchInput query={query} onQueryChange={setQuery} />      <ResultsSummary query={trimmedQuery} />      {!hasQuery && <p>Please enter a search term.</p>}    </>  );};

The UI now grows from one shared state value, rather than from several slightly different interpretations spread around the tree.


Passing Callbacks is Not a Hack

Beginners sometimes feel that sending event handlers down into children is a workaround rather than a real pattern.

It is a real pattern.

The child is saying:

"I do not own this state, but I can notify the parent about a user action."

That is a healthy relationship between the components. It keeps the update path explicit.


When Lifting Starts to Feel Noisy

If state has been lifted and props are now being threaded through several levels of components that do not use them, you may have a prop drilling problem rather than a state ownership problem.

That does not mean lifting state up was wrong. It means the application may now have a separate distribution problem.

This is one of the places where a pattern like context can become useful later on. But context is not the first answer. The first answer is still making sure the state has one clear owner.


The Real Goal is Coherence

Lifting state up is not about obeying a rule from a tutorial. It is about keeping interfaces coherent.

If multiple parts of the page need to agree about one changing piece of information, let one component own that information and let the others derive from it. That is the simplest way to keep the UI honest.

Once you start seeing duplicated state as a warning sign, React architecture becomes much easier to reason about. The question stops being "where can I store this?" and becomes "who really owns this?"

That is a much better design question.


Categories:

  1. Development
  2. Front‑End Development
  3. Guides
  4. JavaScript
  5. React