BehnUm
BehnUm

Reputation: 141

setState in ReactJS

I'm new to ReactJS and I can't seem to find out why the result of the following setState is not as I expect it to be (i.e. to increment the value every second by 1)

import React from 'react';
import ReactDOM from 'react-dom';

class Layout extends React.Component {

    constructor() {
        super();
        this.state = {
            name: "Behnam",
            i: 0
        }
    }

    render() {

        setInterval(() => {
            this.setState({ name : "Behnam" + this.state.i });
            this.setState({ i: this.state.i + 1 });
        }, 1000);

        return (
            <div className="container">
                {this.state.name}
            </div>
        );
    }
}

ReactDOM.render(<Layout />, document.getElementById('app'));

Instead the output string rapidly increases (I guess as fast as react is trying to keep its' virtual DOM updated). So I was wondering what is the right way to do this?

Upvotes: 0

Views: 347

Answers (3)

Ori Drori
Ori Drori

Reputation: 191936

Every time you change the state, you rerender the component. Because you initiated the setInterval in the render method, you get another interval, which changes the state, and rerenders, and so on.

Move the setInterval to componentDidMount, which is invoked only once, when the component mounts:

import React from 'react';
import ReactDOM from 'react-dom';

class Layout extends React.Component {

    constructor() {
        super();
        this.state = {
            name: "Behnam",
            i: 0
        }
    }

    componentDidMount() { set the interval after the component mounted, and save the reference
        this.interval = setInterval(() => {
            this.setState({ 
                name: `Behnam${this.state.i}`, 
                i: this.state.i + 1
            });
        }, 1000);
    }

    componentWillUnmount() {
        this.interval && clearInterval(this.interval); // clear the interval when the component unmounts
    } 

    render() {
        return (
            <div className="container">
                {this.state.name}
            </div>
        );
    }
}

ReactDOM.render(<Layout />, document.getElementById('app'));

Upvotes: 4

Tom Fenech
Tom Fenech

Reputation: 74596

Every time a render is triggered, you're calling setInterval again, adding to the number of active intervals on the page.

You should perhaps make use of another lifecycle method, such as componentDidMount. You should remember to save the interval ID returned by setInterval, so that you can call clearInterval in componentWillUnmount.

Upvotes: 1

dbburgess
dbburgess

Reputation: 761

Currently, it is creating an interval every time the component is rendered, so there are multiple timers incrementing the value. You probably want to do it in componentDidMount() instead of render(). See docs.

import React from 'react';
import ReactDOM from 'react-dom';

class Layout extends React.Component {

    constructor() {
        super();
        this.state = {
            name: "Behnam",
            i: 0
        }
    }

    componentDidMount() {
        setInterval(() => {
            this.setState({ name : "Behnam" + this.state.i });
            this.setState({ i: this.state.i + 1 });
        }, 1000);
    }

    render() {
        return (
            <div className="container">
                {this.state.name}
            </div>
        );
    }
}

ReactDOM.render(<Layout />, document.getElementById('app'));

Upvotes: 1

Related Questions