Search code examples
reactjsreact-state-management

Directly modifying the state of a react component works reliably and consistently?


While looking through the ReactJS code of a colleague, I noticed them "modifying the state directly". I also tried it out with this code:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
        <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
        <script src="https://unpkg.com/babel-standalone@6.26.0/babel.js"></script>
    </head>
    <body>

        <div id="app"></div>
        <script type="text/babel">

        class App extends React.Component {
            constructor(props) {
                super(props);
                this.state = {"message":""};
            }
            textChange(evt) {
                this.state.message = evt.currentTarget.value;
                this.setState({});
            }
            render() {
                return (
                    <div>
                        <p>{this.state.message}</p>
                        <input type="text" onChange={evt=>this.textChange(evt)} />
                    </div>
                );
            }
        }
        ReactDOM.render(<App />, document.getElementById('app'));
        </script>
    </body>
</html>

The method textChange(evt) consistently and reliably updates the state. I read in a few blogs on the internet that we should not be modify the state directly...did I misunderstand something?

Also consider all these other ways of modifying states:

textChange1(evt) {
    this.state.message = evt.currentTarget.value;
    this.setState(this.state);
}
textChange2(evt) {
    const state = this.state;
    state.message = evt.currentTarget.value;
    this.setState(state);
}
textChange3(evt) {
    const state = this.state;
    state.message = evt.currentTarget.value;
    this.setState({});
}
textChange4(evt) {
    this.setState({message:evt.currentTarget.value});
}
textChange5(evt) {
    let message = evt.currentTarget.value;
    this.setState({message});
}

Which of these is the idiomatic way to modify states in ReactJS?


Solution

  • You are correct in that we should never modify state directly (unless it's in the constructor). Doing so may work, but will lead to unintended side effects.

    always create new objects and arrays when you call setState,

    source: https://daveceddia.com/why-not-modify-react-state-directly/

    Do Not Modify State Directly # <--- from react docs itself

    source: https://reactjs.org/docs/state-and-lifecycle.html

    In your example, the below 2 are the best/idiomatic ways to modify state. Note, when you use {} you are initializing an object literal (i.e. creating a new object), which is what the below 2 methods are doing (but that of course in and of itself does not mean it's correct, as you can see with textChange3).

    // great
    textChange4(evt) {
        this.setState({message:evt.currentTarget.value});
    }
    // great
    textChange5(evt) {
        let message = evt.currentTarget.value; // note `let` should be `const` (style-wise) since you are not reassigning message, but otherwise this is fine!
        this.setState({message});
    }
    

    Breaking down each method with comments

    // not good
    textChange1(evt) {
        this.state.message = evt.currentTarget.value; // modifying original state object
        this.setState(this.state);
    }
    // not good
    textChange2(evt) {
        const state = this.state; // this is the exact same as above,
        // except you aliased this.state as state.
        state.message = evt.currentTarget.value;
        this.setState(state);
    }
    // not good
    textChange3(evt) {
        const state = this.state;
        state.message = evt.currentTarget.value; // same problem as above of modifiying state directly
        this.setState({}); // you might expect the state to be set to an empty object, but it's not
        // in this case it's basically a no-op because state updates are merged. see https://reactjs.org/docs/state-and-lifecycle.html
    }
    // great
    textChange4(evt) {
        this.setState({message:evt.currentTarget.value});
    }
    // great
    textChange5(evt) {
        let message = evt.currentTarget.value; // note `let` should be `const` (style-wise) since you are not reassigning message, but otherwise this is fine!
        this.setState({message});
    }