The state is like props, but is completely internal to the component. It can be used only with class components, but not with functional components.

Let's take a look at example from React docs.

const tick = () => {
    const element = (
        <div>
            <h1>It's a dynamic clock !!</h1>
            <h2>It is {new Date().toLocaleTimeString()}</h2>
        </div>
    );
    ReactDOM.render(element, container1);
}
setInterval(tick, 1000);

In this example, we have a function called tick which is being called every second to update DOM with new time. Here we are calling the render method for every second to update the DOM. In practice, most React apps only call render only once. How do we do it, then? we use internal state of class components. Let's rewrite example, using class component.

Create a completely reusable Clock component.

class Clock extends React.Component {
    render() {
        return (
            <div>
                <h1>Its a clock component</h1>
                <h2>It is {this.props.date.toLocaleTimeString()}</h2>
            </div>
        )
    }
}
setInterval(() => {
    ReactDOM.render(
        <Clock date={new Date()}/>, container2);
}, 1000);

We have now created a clock component which is rendered for every second. Take look at a clock component, its accepting a prop called, date and a new date object is being passed every time it is called. So far good, still the component is not effective because of 2 reasons.

  1. Clock is not updating on its own, we are updating it for every second by calling ReactDOM. render method.
  2. Clock is accepting an input called date. In real world clock works on its own, it is not told by anyone what the time is?
class Clock extends React.Component {
    // Es6 feature 
    constructor(props) {
        super(props);
        this.state = { date: new Date() };
    }

    // Lifecycle method triggerd when Component added to DOM.
    componentDidMount() {
        this.timerId = setInterval(() => this.tick(), 1000);
    }

    //Lifecycle method triggerd when Component removed from DOM.
    componentWillUnmount() {
        clearInterval(this.timerId);
    }

    tick() {
        this.setState({
            date: new Date()
        })
    }

    render() {
        return (
            <div>
                <h1>Its a self contained Independent Clock</h1>
                <h2>It is {this.state.date.toLocaleTimeString()}</h2>
            </div>
        )
    }
}
ReactDOM.render(<Clock />, container3);

Let's quickly recap what's going on and the order in which the methods are called:

1) When is passed to ReactDOM.render(), React calls the constructor of the Clock component. Since Clock needs to display the current time, it initializes this.state with an object including the current time. We will later update this state.

2) React then calls the Clock component's render() method. This is how React learns what should be displayed on the screen. React then updates the DOM to match the Clock's render output.

3) When the Clock output is inserted in the DOM, React calls the componentDidMount() lifecycle hook. Inside it, the Clock component asks the browser to set up a timer to call tick() once a second.

4) Every second the browser calls the tick() method. Inside it, the Clock component schedules a UI update by calling setState() with an object containing the current time. Thanks to the setState() call, React knows the state has changed, and calls render() method again to learn what should be on the screen. This time, this.state.date in the render() method will be different, and so the render output will include the updated time. React updates the DOM accordingly.

5) If the Clock component is ever removed from the DOM, React calls the componentWillUnmount() lifecycle hook so the timer is stopped.

State if used, must be initialized inside constructor.
setState() will always trigger rendering of component

Using State Correctly

There are three things you should know about setState().

Do Not Modify State Directly (From React Docs)

For example, this will not re-render a component:

// Wrong
this.state.date = new Date();

Instead, use setState()

// Correct
this.setState({date: new Date()});
The only place where you can assign this.state is the constructor.

State Updates May Be Asynchronous

React may batch multiple setState() calls into a single update for performance.

It means that the setState method can be asynchronous at times. Updating state multiple times within the same function may not give the desired output.

Create a button which will display green and red with even and odd clicks respectively:

    class Counter extends React.Component {

        constructor(props) {
            super(props);
            this.state = {
                counter: 0,
                counterClass: 'btn-success'
            }
            this.increment = this.increment.bind(this);
        }

        increment() {
            // increases counter by 1
            this.setState({
                counter: this.state.counter + 1
            });
            let counterClass = 'btn-danger';
            // set counterClass based even or odd.
            if(this.state.counter % 2 === 0) {
                counterClass = 'btn-success';
            }
            //update counterClass variable in state.
            this.setState({
                counterClass: counterClass
            });
        }

        render() {
            return (
                <div>
                    <a href='#' id='btn' className={`btn btn-sm ${this.state.counterClass}`} 
                        onClick={this.increment} >
                        <h4>Counter: {this.state.counter}</h4>
                    </a>
                </div>
            )
        }

    }

    ReactDOM.render(<Counter />, container4);

Let's quickly go through what we have done:

1) Created a Counter component with internal state having counter and counterClass.

2) Created a onClick event listener called increment and updating internal state.

3) Attached our Counter component to container 7.

So far good, our expectation from the component is to display either green or red button based on even or odd output, but the button doesn't behave like what we expected it to be, Reason is

setState changes are updated in batches i.e. all the updates in a single function are batched and then updated at once. This is mainly for improving performance.

this.setState({
    counter: this.state.counter + 1
});

This code will not update the counter variable immediately when you set. It is kept in queue and will execute once the function execution is done. As an effect

if(this.state.counter % 2 === 0) {
    counterClass = 'btn-success';
}

counterClass state is set based on previous counter state, but not on updated counter state because state changes are batched and yet to be executed.

setState() does not immediately change this.state but creates a queue of changes that needs execution. As a result when this.state is accessed after calling setState can return the existing value and not the updated value.
Change in internal state makes UI screen to re-render. Re-rendering is expensive operation, so making internal state changes synchronous might make browser unresponsive. Batch updates provide better UI experience and performance.

There are couple of ways to deal with these kind of situations.

  1. Using callback function as a second parameter for setState method. This callback function is triggered once the state is updated. Let's change the example to use callback
    class Counter extends React.Component {

        constructor(props) {
            super(props);
            this.state = {
                counter: 0,
                counterClass: 'btn-success'
            }
            this.increment = this.increment.bind(this);
        }

        increment() {
            this.setState({
                counter: this.state.counter + 1
            }, function() { // callback triggered when state updated
                let counterClass = 'btn-danger';
                if(this.state.counter % 2 === 0) {
                    counterClass = 'btn-success';
                }
                this.setState({ counterClass: counterClass });
            });
        }

        render() {
            return (
                <div>
                    <a href='#' id='btn' className={`btn btn-sm ${this.state.counterClass}`} 
                        onClick={this.increment} >
                        <h4>Counter: {this.state.counter}</h4>
                    </a>
                </div>
            )
        }

    }

    ReactDOM.render(<Counter />, container5);

Though it looks simple using callback is not recommended for state changes because code becomes more complicated if state changes are dependent on other state changes and also code becomes difficult to maintain.

  1. Instead of object setState also accepts a function with 2 parameters prevState and nextProps and returns updated state. This is the most recommended way for updating state. Let's change example to use this method
    class Counter extends React.Component {

        constructor(props) {
            super(props);
            this.state = {
                counter: 0,
                counterClass: 'btn-success'
            }
            this.increment = this.increment.bind(this);
        }

        increment() {
            this.setState((prevState, nextProps) => {
                const state  = prevState;
                let counterClass = 'btn-danger';
                state.counter = state.counter + 1;
                if(state.counter % 2 === 0) {
                    counterClass = 'btn-success';
                }
                state.counterClass = counterClass;
                return state;
            })
        }

        render() {
            return (
                <div>
                    <a href="#" id="btn" className={'btn btn-md ' + this.state.counterClass} 
                        onClick={this.increment} >
                        <h4>Counter: {this.state.counter}</h4>
                    </a>
                </div>
            )
        }
    }

    ReactDOM.render(<Counter />, container6);

State updates are asynchronous only when setState is called in React context like onClick, onMouseEnter etc... If you call setState method outside React context, then every call to setState is synchronous

Lets change the example to implement onClick outside React context.

    class Counter extends React.Component {

        constructor(props) {
            super(props);
            this.state = {
                counter: 0,
                counterClass: 'btn-success'
            }
        }

        componentDidMount() {
            const ele = document.getElementById('syncStateBtn');
            // React cannot batch the state changes because eventlistener is not in react context.
            ele.addEventListener('click', function() {
                this.setState({
                    counter: this.state.counter + 1
                })
                let counterClass = 'btn-danger';
                if(this.state.counter % 2 === 0) {
                    counterClass = 'btn-success';
                }
                this.setState({
                    counterClass: counterClass
                });
            }.bind(this))
        }

        render() {
            return (
                <div>
                    <a href="#" id="syncStateBtn" className={'btn btn-md ' + this.state.counterClass} >
                        <h4>Counter: {this.state.counter}</h4>
                    </a>
                </div>
            )
        }
    }

    ReactDOM.render(<Counter />, container7);

Instead of using React's onClick event listener, we have choose to write our own event listener. In this case React cannot batch the updates, so setState will change the state immediately.

setState will trigger an asynchronous action or batch updates when things run inside React Context like onClick, onKeyUp etc... It creates issues with state if you refer to it immediately. Use setState((prevState, nextProps) => ({}) variation to overcome state update issues.
setState will trigger an synchronous action when things run outside React Context like addEventListener, timeout functions etc... It immediately updates the state and re-renders. No special care needed in this case

State Updates are Merged (From React Docs)

When you call setState(), React merges the object you provide into the current state.

For example, your state may contain several independent variables:

constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
}

Then you can update them independently with separate setState() calls:

componentDidMount() {
    fetchPosts().then(response => {
        this.setState({
        posts: response.posts
        });
    });

    fetchComments().then(response => {
        this.setState({
        comments: response.comments
        });
    });
}

The merging is shallow, so this.setState({comments}) leaves this.state.posts intact, but completely replaces this.state.comments.

results matching ""

    No results matching ""