I'm reading through the Lifting Up State guide on the React docs.
It seems to me that this refactor creates a dependency in the TemperatureInput
component on its parent, whatever it is, to provide data.
What if I wanted to use the TemperatureInput
component somewhere else in my app where I don't need this kind of data binding? Wouldn't that dependency on the component's parent still exist, meaning that any component containing it would need to provide that state?
This seems to violate the idea that data should be held as close to where it's used as possible. The parent doesn't necessarily need to know the value of this component, but because of the component's design, the parent is forced to define and contain that value.
CodePudding user response:
It seems to me that this refactor creates a dependency in the TemperatureInput component on its parent, whatever it is, to provide data.
It does. And the reason they did this for this case is because they want anything entered in one input to be mirrored in the other. Some component is going to need to manage the state to keep the two linked, and the parent component is the most natural place to do that.
As they said, this is called "lifting state up", but another description of what they've done is change TemperatureInput
to be a controlled component. Controlled components are ones that are pretty dumb on their own, just taking their instructions from a parent component. This makes them very flexible, since that parent can implement whatever logic it likes. But it does mean that the parent has to implement that.
The opposite of a controlled component is an uncontrolled component. Uncontrolled components may take some initial values from the parent, but after that they handle things themselves. This makes them less flexible, since they have to already have the functionality inside them, but it means they can usually be used with less effort.
One place you may have encountered controlled vs uncontrolled components is in the standard dom elements, such as <input>
s. An input can be used in a controlled manner by passing value
and onChange
props, which results it being controlled by the parent component. Or, you can pass in defaultValue
, and let the input handle the rest.
Both styles (controlled and uncontrolled) have their uses, and it just depends on what the needs of your app are what you should use for this component. But you're not limited to one or the other: as the <input>
component demonstrates, it's possible for you to support both modes of operation in a single component. So if you have one page where the temperature inputs need to be linked, and another where they don't, you could add some extra code to TemperatureInput so it can work with both:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
temperature: props.defaultTemperature ?? ''
};
}
handleChange(e) {
if (this.props.onTemperatureChange) {
// Prop exists. We're in controlled component mode.
this.props.onTemperatureChange(e.target.value);
} else {
// Uncontrolled component
this.setState({temperature: e.target.value});
}
}
render() {
// If the prop exists, use it (ie, controlled component)
// Otherwise, use our state (uncontrolled component)
const temperature = this.props.temperature ?? this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature} onChange={this.handleChange} />
</fieldset>
);
}
}