Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known aslifting state up, and it’s one of the most common things you will do writing React code.
You will learn
- How to share state between components by lifting it up
- What are controlled and uncontrolled components
Lifting state up by example
In this example, a parentAccordion component renders two separatePanels:
AccordionPanelPanel
EachPanel component has a booleanisActive state that determines whether its content is visible.
Press the Show button for both panels:
import{useState}from'react';functionPanel({title,children}){const[isActive,setIsActive] =useState(false);return(<sectionclassName="panel"><h3>{title}</h3>{isActive ?(<p>{children}</p>) :(<buttononClick={()=>setIsActive(true)}> Show</button>)}</section>);}exportdefaultfunctionAccordion(){return(<><h2>Almaty, Kazakhstan</h2><Paneltitle="About"> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.</Panel><Paneltitle="Etymology"> The name comes from<spanlang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild<ilang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.</Panel></>);}
Notice how pressing one panel’s button does not affect the other panel—they are independent.


Initially, eachPanel’sisActive state isfalse, so they both appear collapsed


Clicking eitherPanel’s button will only update thatPanel’sisActive state alone
But now let’s say you want to change it so that only one panel is expanded at any given time. With that design, expanding the second panel should collapse the first one. How would you do that?
To coordinate these two panels, you need to “lift their state up” to a parent component in three steps:
- Remove state from the child components.
- Pass hardcoded data from the common parent.
- Add state to the common parent and pass it down together with the event handlers.
This will allow theAccordion component to coordinate bothPanels and only expand one at a time.
Step 1: Remove state from the child components
You will give control of thePanel’sisActive to its parent component. This means that the parent component will passisActive toPanel as a prop instead. Start byremoving this line from thePanel component:
const[isActive,setIsActive] =useState(false);And instead, addisActive to thePanel’s list of props:
functionPanel({title,children,isActive}){Now thePanel’s parent component cancontrolisActive bypassing it down as a prop. Conversely, thePanel component now hasno control over the value ofisActive—it’s now up to the parent component!
Step 2: Pass hardcoded data from the common parent
To lift state up, you must locate the closest common parent component ofboth of the child components that you want to coordinate:
Accordion(closest common parent)PanelPanel
In this example, it’s theAccordion component. Since it’s above both panels and can control their props, it will become the “source of truth” for which panel is currently active. Make theAccordion component pass a hardcoded value ofisActive (for example,true) to both panels:
import{useState}from'react';exportdefaultfunctionAccordion(){return(<><h2>Almaty, Kazakhstan</h2><Paneltitle="About"isActive={true}> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.</Panel><Paneltitle="Etymology"isActive={true}> The name comes from<spanlang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild<ilang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.</Panel></>);}functionPanel({title,children,isActive}){return(<sectionclassName="panel"><h3>{title}</h3>{isActive ?(<p>{children}</p>) :(<buttononClick={()=>setIsActive(true)}> Show</button>)}</section>);}
Try editing the hardcodedisActive values in theAccordion component and see the result on the screen.
Step 3: Add state to the common parent
Lifting state up often changes the nature of what you’re storing as state.
In this case, only one panel should be active at a time. This means that theAccordion common parent component needs to keep track ofwhich panel is the active one. Instead of aboolean value, it could use a number as the index of the activePanel for the state variable:
const[activeIndex,setActiveIndex] =useState(0);When theactiveIndex is0, the first panel is active, and when it’s1, it’s the second one.
Clicking the “Show” button in eitherPanel needs to change the active index inAccordion. APanel can’t set theactiveIndex state directly because it’s defined inside theAccordion. TheAccordion component needs toexplicitly allow thePanel component to change its state bypassing an event handler down as a prop:
<>
<Panel
isActive={activeIndex ===0}
onShow={()=>setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex ===1}
onShow={()=>setActiveIndex(1)}
>
...
</Panel>
</>The<button> inside thePanel will now use theonShow prop as its click event handler:
import{useState}from'react';exportdefaultfunctionAccordion(){const[activeIndex,setActiveIndex] =useState(0);return(<><h2>Almaty, Kazakhstan</h2><Paneltitle="About"isActive={activeIndex ===0}onShow={()=>setActiveIndex(0)}> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.</Panel><Paneltitle="Etymology"isActive={activeIndex ===1}onShow={()=>setActiveIndex(1)}> The name comes from<spanlang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild<ilang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.</Panel></>);}functionPanel({title,children,isActive,onShow}){return(<sectionclassName="panel"><h3>{title}</h3>{isActive ?(<p>{children}</p>) :(<buttononClick={onShow}> Show</button>)}</section>);}
This completes lifting state up! Moving state into the common parent component allowed you to coordinate the two panels. Using the active index instead of two “is shown” flags ensured that only one panel is active at a given time. And passing down the event handler to the child allowed the child to change the parent’s state.


Initially,Accordion’sactiveIndex is0, so the firstPanel receivesisActive = true


WhenAccordion’sactiveIndex state changes to1, the secondPanel receivesisActive = true instead
Deep Dive
It is common to call a component with some local state “uncontrolled”. For example, the originalPanel component with anisActive state variable is uncontrolled because its parent cannot influence whether the panel is active or not.
In contrast, you might say a component is “controlled” when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. The finalPanel component with theisActive prop is controlled by theAccordion component.
Uncontrolled components are easier to use within their parents because they require less configuration. But they’re less flexible when you want to coordinate them together. Controlled components are maximally flexible, but they require the parent components to fully configure them with props.
In practice, “controlled” and “uncontrolled” aren’t strict technical terms—each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer.
When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state). But you can always change your mind and refactor later.
A single source of truth for each state
In a React application, many components will have their own state. Some state may “live” close to the leaf components (components at the bottom of the tree) like inputs. Other state may “live” closer to the top of the app. For example, even client-side routing libraries are usually implemented by storing the current route in the React state, and passing it down by props!
For each unique piece of state, you will choose the component that “owns” it. This principle is also known as having a“single source of truth”. It doesn’t mean that all state lives in one place—but that foreach piece of state, there is aspecific component that holds that piece of information. Instead of duplicating shared state between components,lift it up to their common shared parent, andpass it down to the children that need it.
Your app will change as you work on it. It is common that you will move state down or back up while you’re still figuring out where each piece of the state “lives”. This is all part of the process!
To see what this feels like in practice with a few more components, readThinking in React.
Recap
- When you want to coordinate two components, move their state to their common parent.
- Then pass the information down through props from their common parent.
- Finally, pass the event handlers down so that the children can change the parent’s state.
- It’s useful to consider components as “controlled” (driven by props) or “uncontrolled” (driven by state).
Challenge 1 of 2:Synced inputs
These two inputs are independent. Make them stay in sync: editing one input should update the other input with the same text, and vice versa.
import{useState}from'react';exportdefaultfunctionSyncedInputs(){return(<><Inputlabel="First input"/><Inputlabel="Second input"/></>);}functionInput({label}){const[text,setText] =useState('');functionhandleChange(e){setText(e.target.value);}return(<label>{label}{' '}<inputvalue={text}onChange={handleChange}/></label>);}