Lifting State Up (Shared State)
# 09. Lifting State Up (Shared State)
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
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".
An example of two input fields sharing data:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
// Convert to Celsius
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
// Convert to Fahrenheit
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
// Convert: returns empty for empty input, otherwise returns a float rounded to 3 decimal places
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
// Math.round returns the nearest integer
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
// Whether water would boil
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
// Child component - input field
class TemperatureInput extends React.Component {
constructor(props) {
super(props); // Receive props from parent
this.handleChange = this.handleChange.bind(this); // Bind callback and fix this
}
// Handle change
handleChange(e) {
// e is a synthetic event object, access value via e.target.value
// Call the onTemperatureChange function passed from the parent, and pass the value
this.props.onTemperatureChange(e.target.value);
// When the child input value changes, call the parent's onTemperatureChange method and emit the value.
// Also, the naming convention for onTemperatureChange: `on<ChildComponent>Change`
}
render() {
// Receive temperature value from parent
const temperature = this.props.temperature;
// Receive scale from parent
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
// Parent component - calculator
class Calculator extends React.Component {
constructor(props) {
super(props); // Receive props from parent
// Bind event callbacks and fix this
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
// Create initial state values
this.state = {temperature: '', scale: 'c'};
}
// Handle Celsius change
handleCelsiusChange(temperature) {
// temperature receives the argument from the child, and modifies state via setState
this.setState({scale: 'c', temperature});
}
// Handle Fahrenheit change
handleFahrenheitChange(temperature) {
// temperature receives the argument from the child, and modifies state via setState
this.setState({scale: 'f', temperature});
}
// Render function (called whenever state changes)
render() {
// Get current state values
const scale = this.state.scale;
const temperature = this.state.temperature;
// Get the corresponding temperature data based on scale
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
// Return the rendered elements
// Insert TemperatureInput child components with appropriate parameters, onTemperatureChange set to callback functions
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
// Render to DOM
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
Try it on CodePen (opens new window)
Note
- The parent passes state values to all child components
- When a child modifies a value, it calls the parent's method and passes the value out
- The parent receives the value and modifies state
- After state is modified, the render function re-executes, returning to step 1
# Summary
Any mutable data should have a single corresponding "source of truth"
- Usually, 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
- You should rely on the top-down data flow (opens new window), rather than trying to sync state between different components
Any state that "lives" in a component can only be modified by that component itself
If some data can be derived from either props or state, it probably shouldn't be in the state. (Like the values obtained via the tryConvert method in the example above.)
# React Developer Tools (Debug)
When you see something wrong in the UI, you can use React Developer Tools (opens new window) to inspect the props of the problem component and search up the component tree until you find the component responsible for updating the state.