These docs are old and won’t be updated. Go toreact.dev for the new React docs.
These new documentation pages teach modern React and include live examples:
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor. Let’s see how this works in action.
In this section, we will create a temperature calculator that calculates whether the water would boil at a given temperature.
We will start with a component calledBoilingVerdict. It accepts thecelsius temperature as a prop, and prints whether it is enough to boil the water:
functionBoilingVerdict(props){if(props.celsius>=100){return<p>The water would boil.</p>;}return<p>The water would not boil.</p>;}Next, we will create a component calledCalculator. It renders an<input> that lets you enter the temperature, and keeps its value inthis.state.temperature.
Additionally, it renders theBoilingVerdict for the current input value.
classCalculatorextendsReact.Component{constructor(props){super(props);this.handleChange=this.handleChange.bind(this);this.state={temperature:''};}handleChange(e){this.setState({temperature: e.target.value});}render(){const temperature=this.state.temperature;return(<fieldset><legend>Enter temperature in Celsius:</legend><inputvalue={temperature}onChange={this.handleChange}/><BoilingVerdictcelsius={parseFloat(temperature)}/></fieldset>);}}Our new requirement is that, in addition to a Celsius input, we provide a Fahrenheit input, and they are kept in sync.
We can start by extracting aTemperatureInput component fromCalculator. We will add a newscale prop to it that can either be"c" or"f":
const scaleNames={c:'Celsius',f:'Fahrenheit'};classTemperatureInputextendsReact.Component{constructor(props){super(props);this.handleChange=this.handleChange.bind(this);this.state={temperature:''};}handleChange(e){this.setState({temperature: e.target.value});}render(){const temperature=this.state.temperature;const scale=this.props.scale;return(<fieldset><legend>Enter temperature in{scaleNames[scale]}:</legend><inputvalue={temperature}onChange={this.handleChange}/></fieldset>);}}We can now change theCalculator to render two separate temperature inputs:
classCalculatorextendsReact.Component{render(){return(<div><TemperatureInputscale="c"/><TemperatureInputscale="f"/></div>);}}We have two inputs now, but when you enter the temperature in one of them, the other doesn’t update. This contradicts our requirement: we want to keep them in sync.
We also can’t display theBoilingVerdict fromCalculator. TheCalculator doesn’t know the current temperature because it is hidden inside theTemperatureInput.
First, we will write two functions to convert from Celsius to Fahrenheit and back:
functiontoCelsius(fahrenheit){return(fahrenheit-32)*5/9;}functiontoFahrenheit(celsius){return(celsius*9/5)+32;}These two functions convert numbers. We will write another function that takes a stringtemperature and a converter function as arguments and returns a string. We will use it to calculate the value of one input based on the other input.
It returns an empty string on an invalidtemperature, and it keeps the output rounded to the third decimal place:
functiontryConvert(temperature, convert){const input=parseFloat(temperature);if(Number.isNaN(input)){return'';}const output=convert(input);const rounded= Math.round(output*1000)/1000;return rounded.toString();}For example,tryConvert('abc', toCelsius) returns an empty string, andtryConvert('10.22', toFahrenheit) returns'50.396'.
Currently, bothTemperatureInput components independently keep their values in the local state:
classTemperatureInputextendsReact.Component{constructor(props){super(props);this.handleChange=this.handleChange.bind(this);this.state={temperature:''};}handleChange(e){this.setState({temperature: e.target.value});}render(){const temperature=this.state.temperature;// ...However, we want these two inputs to be in sync with each other. When we update the Celsius input, the Fahrenheit input should reflect the converted temperature, and vice versa.
In React, sharing state is accomplished by moving it up to the closest common ancestor of the components that need it. This is called “lifting state up”. We will remove the local state from theTemperatureInput and move it into theCalculator instead.
If theCalculator owns the shared state, it becomes the “source of truth” for the current temperature in both inputs. It can instruct them both to have values that are consistent with each other. Since the props of bothTemperatureInput components are coming from the same parentCalculator component, the two inputs will always be in sync.
Let’s see how this works step by step.
First, we will replacethis.state.temperature withthis.props.temperature in theTemperatureInput component. For now, let’s pretendthis.props.temperature already exists, although we will need to pass it from theCalculator in the future:
render(){// Before: const temperature = this.state.temperature;const temperature=this.props.temperature;// ...We know thatprops are read-only. When thetemperature was in the local state, theTemperatureInput could just callthis.setState() to change it. However, now that thetemperature is coming from the parent as a prop, theTemperatureInput has no control over it.
In React, this is usually solved by making a component “controlled”. Just like the DOM<input> accepts both avalue and anonChange prop, so can the customTemperatureInput accept bothtemperature andonTemperatureChange props from its parentCalculator.
Now, when theTemperatureInput wants to update its temperature, it callsthis.props.onTemperatureChange:
handleChange(e){// Before: this.setState({temperature: e.target.value});this.props.onTemperatureChange(e.target.value);// ...Note:
There is no special meaning to either
temperatureoronTemperatureChangeprop names in custom components. We could have called them anything else, like name themvalueandonChangewhich is a common convention.
TheonTemperatureChange prop will be provided together with thetemperature prop by the parentCalculator component. It will handle the change by modifying its own local state, thus re-rendering both inputs with the new values. We will look at the newCalculator implementation very soon.
Before diving into the changes in theCalculator, let’s recap our changes to theTemperatureInput component. We have removed the local state from it, and instead of readingthis.state.temperature, we now readthis.props.temperature. Instead of callingthis.setState() when we want to make a change, we now callthis.props.onTemperatureChange(), which will be provided by theCalculator:
classTemperatureInputextendsReact.Component{constructor(props){super(props);this.handleChange=this.handleChange.bind(this);}handleChange(e){this.props.onTemperatureChange(e.target.value);}render(){const temperature=this.props.temperature;const scale=this.props.scale;return(<fieldset><legend>Enter temperature in{scaleNames[scale]}:</legend><inputvalue={temperature}onChange={this.handleChange}/></fieldset>);}}Now let’s turn to theCalculator component.
We will store the current input’stemperature andscale in its local state. This is the state we “lifted up” from the inputs, and it will serve as the “source of truth” for both of them. It is the minimal representation of all the data we need to know in order to render both inputs.
For example, if we enter 37 into the Celsius input, the state of theCalculator component will be:
{temperature:'37',scale:'c'}If we later edit the Fahrenheit field to be 212, the state of theCalculator will be:
{temperature:'212',scale:'f'}We could have stored the value of both inputs but it turns out to be unnecessary. It is enough to store the value of the most recently changed input, and the scale that it represents. We can then infer the value of the other input based on the currenttemperature andscale alone.
The inputs stay in sync because their values are computed from the same state:
classCalculatorextendsReact.Component{constructor(props){super(props);this.handleCelsiusChange=this.handleCelsiusChange.bind(this);this.handleFahrenheitChange=this.handleFahrenheitChange.bind(this);this.state={temperature:'',scale:'c'};}handleCelsiusChange(temperature){this.setState({scale:'c', temperature});}handleFahrenheitChange(temperature){this.setState({scale:'f', temperature});}render(){const scale=this.state.scale;const temperature=this.state.temperature;const celsius= scale==='f'?tryConvert(temperature, toCelsius): temperature;const fahrenheit= scale==='c'?tryConvert(temperature, toFahrenheit): temperature;return(<div><TemperatureInputscale="c"temperature={celsius}onTemperatureChange={this.handleCelsiusChange}/><TemperatureInputscale="f"temperature={fahrenheit}onTemperatureChange={this.handleFahrenheitChange}/><BoilingVerdictcelsius={parseFloat(celsius)}/></div>);}}Now, no matter which input you edit,this.state.temperature andthis.state.scale in theCalculator get updated. One of the inputs gets the value as is, so any user input is preserved, and the other input value is always recalculated based on it.
Let’s recap what happens when you edit an input:
onChange on the DOM<input>. In our case, this is thehandleChange method in theTemperatureInput component.handleChange method in theTemperatureInput component callsthis.props.onTemperatureChange() with the new desired value. Its props, includingonTemperatureChange, were provided by its parent component, theCalculator.Calculator had specified thatonTemperatureChange of the CelsiusTemperatureInput is theCalculator’shandleCelsiusChange method, andonTemperatureChange of the FahrenheitTemperatureInput is theCalculator’shandleFahrenheitChange method. So either of these twoCalculator methods gets called depending on which input we edited.Calculator component asks React to re-render itself by callingthis.setState() with the new input value and the current scale of the input we just edited.Calculator component’srender method to learn what the UI should look like. The values of both inputs are recomputed based on the current temperature and the active scale. The temperature conversion is performed here.render methods of the individualTemperatureInput components with their new props specified by theCalculator. It learns what their UI should look like.render method of theBoilingVerdict component, passing the temperature in Celsius as its props.Every update goes through the same steps so the inputs stay in sync.
There should be a single “source of truth” for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on thetop-down data flow.
Lifting state involves writing more “boilerplate” code than two-way binding approaches, but as a benefit, it takes less work to find and isolate bugs. Since any state “lives” in some component and that component alone can change it, the surface area for bugs is greatly reduced. Additionally, you can implement any custom logic to reject or transform user input.
If something can be derived from either props or state, it probably shouldn’t be in the state. For example, instead of storing bothcelsiusValue andfahrenheitValue, we store just the last editedtemperature and itsscale. The value of the other input can always be calculated from them in therender() method. This lets us clear or apply rounding to the other field without losing any precision in the user input.
When you see something wrong in the UI, you can useReact Developer Tools to inspect the props and move up the tree until you find the component responsible for updating the state. This lets you trace the bugs to their source:
