How to handle state in React. The missing FAQ.

Osmel Mora
Jul 18, 2016 · 6 min read
Image for post
Image for post
Image for post
Image for post

Motivation

Recently there has been a lot of debate about how to manage the state in React. Some claim that setState() doesn’t work as you might expect. That you have to externalize the state using a Flux-like state container and avoid completely the component’s state.

  • In order to embrace all the power React gives you, it is crucial a solid understanding of how to handle state.
  • Don’t add another layer of complexity to your application if you don’t need it. Remember: simplicity matters.

Frequently Asked Questions.

How does state work?

A React component is like a state machine that represents a user interface. Every user action potentially triggers a change in that state machine. Then, the new state is represented by a different React element.

// Using React.createClass
var Counter = React.createClass({
getInitialState: function() {
return {counter: 0};
},
...
});// Using ES6 classes
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {counter: 0};
}
...
}
this.setState(data, callback);

What should I keep in React state?

Dan Abramov answered this question with one tweet:

// Don't duplicate data from props in state
// Antipattern
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {message: props.message};
}

render() {
return <div>{this.state.message}</div>;
}
}
// Better exampleclass Component extends React.Component {    
render() {
return <div>{this.props.message}</div>;
}
}
// Don't hold state based on props calculation
// Antipattern
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {fullName: `${props.name} ${props.lastName}`};
}

render() {
return <div>{this.state.fullName}</div>;
}
}
// Better approachclass Component extends React.Component {
render() {
const {name, lastName} = this.props;
return <div>{`${name} ${lastName}`}</div>;
}
}
// Not an antipatternclass Component extends React.Component {
constructor(props) {
super(props);
this.state = {count: props.initialCount};
this.onClick = this.onClick.bind(this);
}

render() {
return <div onClick={this.onClick}>{this.state.count}</div>;
}

onClick() {
this.setState({count: this.state.count + 1});
}
}
// Don't hold state that you don't use for rendering.
// Leads to unneeded re-renders and other inconsistencies.
// Antipattern
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}

render() {
return <div>{this.state.count}</div>;
}

componentDidMount() {
const interval = setInterval(() => (
this.setState({count: this.state.count + 1})
), 1000);
this.setState({interval});
}
componentWillUnmount() {
clearInterval(this.state.interval);
}
}
//Better approachclass Component extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}

render() {
return <div>{this.state.count}</div>;
}

componentDidMount() {
this._interval = setInterval(() => (
this.setState({count: this.state.count + 1})
), 1000);
}
componentWillUnmount() {
clearInterval(this._interval);
}
}

Is it true that setState( ) is asynchronous?

The short answer: Yes.

Image for post
Image for post
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
this.onClick = this.onClick.bind(this);
}

render() {
return <div onClick={this.onClick}>{this.state.count}</div>;
}

onClick() {
this.setState({count: this.state.count + 1});
console.log(this.state.count);

}
}
// Calling setState() twice in the same execution context is a bad // practice. It's used here for illustration purposes. Instead use // an atomic update in real code class Component extends React.Component {
constructor(props) {
super(props);
this.state = {count: 0};
}

render() {
return <div>{this.state.count}</div>;
}

componentDidMount() {
this._interval = setInterval(() => {
this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);

}, 1000);
}
componentWillUnmount() {
clearInterval(this._interval);
}
}

I heard that under certain circumstances calling setState( ) doesn’t trigger a re-render. Which are those circumstances?

  1. When you call setState() within componentWillMount() or componentWillRecieveProps() it won’t trigger any additional re-render. React batches the updates.
  2. When you return false from shouldComponentUpdate(). This way the render() method is completely skipped along with componentWillUpdate() and componentDidUpdate().

Conclusions

Properly handling state in React can be a challenge. I hope by now you have a clearer picture.

React Ecosystem

All about React and its ecosystem

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store