You might've heard about Lifting Your State hundreds of times before but have really been unable to comprehend what does it actually mean, neither do I.
So, I looked up the official docs and the docs say- Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
Let's understand this with a simple example.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class Counter extends React.Component {
state = {
counter: 0
};
decrement = () => {
this.setState({ counter: this.state.counter - 1 });
};
increment = () => {
this.setState({ counter: this.state.counter + 1 });
};
render() {
return (
<>
<button type="button" onClick={this.decrement}>
-
</button>
{this.state.counter}
<button type="button" onClick={this.increment}>
+
</button>
</>
);
}
}
class App extends React.Component {
render() {
return (
<div className="App">
<Counter />
<p>You have clicked 0 times!</p>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
First off, we have a Counter component. This component has the state initialized with a value of counter set to zero. Also, we have increment and decrement methods. The output would something look like this.
Now, we want "You clicked 0 times" to increment on clicking the button. In order to do that, could we add
clicks: 0
field in Counter like this and pass this data in the state to the App component? I don't see any other solution.
class Counter extends React.Component {
state = {
counter: 0,
clicks: 0
};
////
////
class App extends React.Component {
render() {
return (
<div className="App">
<Counter />
<p>You have clicked 0 times!</p> // how do I update this ?
</div>
);
}
}
Well, I don't think that's quite possible.
So, the solution would be lift the state up. Instead of keeping the state in Counter, we move the state to our App component. See here.
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class Counter extends React.Component {
render() {
const { increment, counter, decrement } = this.props;
return (
<>
<button type="button" onClick={decrement}>
-
</button>
{counter}
<button type="button" onClick={increment}>
+
</button>
</>
);
}
}
class App extends React.Component {
state = {
clicks: 0,
counter: 0
};
decrement = () => {
this.setState({
counter: this.state.counter - 1,
clicks: this.state.clicks + 1
});
};
increment = () => {
this.setState({
counter: this.state.counter + 1,
clicks: this.state.clicks + 1
});
};
render() {
const { clicks, counter } = this.state;
return (
<div className="App">
<Counter
increment={this.increment}
decrement={this.decrement}
counter={counter}
/>
<p>You have clicked {clicks} times!</p>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Now, the funny part is, you might've done this unknowingly before or you'd consider this approach to be the general way of doing it. I totally agree with you, but yeah, that's the whole lifting state up fuzz is. Even the docs definition would make some sense now.
Try the CodeSandBox here.
Thank You for reading this article. Follow me on Twitter for more updates.
Top comments (2)
This, in turn, is why Redux is such a common part of React development: While this approach is very sensible for relatively small projects - or when the shared state can be kept contained one or two levels up - it becomes very difficult for large cross-referencing apps.
Personally, I try to keep lifting limited to two levels, and beyond that I start to consider two options:
Yes, you're right.
I think using a state store will definitely solve most of your problems. Lifting state up, prop drilling, whatever you say, I've found Redux to be really helpful when it comes to bigger projects. A single source of truth sounds cool.😊😊