Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting state in render by using this.state

I've recently seen this type of react pattern where the state is being set in a render by using this.state:

class ShowMe extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            showButton: false,
        };
    }

    render() {
        if (this.props.show) {
            this.state.showButton = true; //setting state in render!!
        }

        return (
            <div>
                <div> Show or hide button </div>
                {this.state.showButton && <Button content='Btn'/>}
            </div>
        )
    }
}

This seems like an anti-pattern. Can this cause bugs? It seems to work properly though.

I would just use a component lifecycle to set the state:

class ShowMe extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            showButton: false,
        };
    }

    componentWillReceiveProps(nextProps) {
        if(nextProps.show) {
            this.setState({
                showButton: true,         
            })
        }
     }

    render() {
        return (
            <div>
                <div> Show or hide button </div>
                {this.state.showButton && <Button content='Btn'/>}
            </div>
        )
    }
}

What is the recommended way?

like image 975
user2456977 Avatar asked Apr 18 '18 14:04

user2456977


3 Answers

render should always be pure without any side effects, so it's certainly a bad practice.

from the React docs :

The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser. If you need to interact with the browser, perform your work in componentDidMount() or the other lifecycle methods instead. Keeping render() pure makes components easier to think about.

Take a look also here and here.

like image 174
Hamza Fatmi Avatar answered Oct 21 '22 12:10

Hamza Fatmi


It is an anti-pattern. If showButton state is not always equal to show props (which is the case in the example), I would use this:

class ShowMe extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            showButton: this.props.show,
        };
    }

    componentDidUpdate(prevProps, prevState) {
        prevProps.show !== this.props.show && this.setState({showButton: this.props.show})
    }

    render() {

        return (
            <div>
                <div> Show or hide button </div>
                {this.state.showButton && <Button content='Btn'/>}
            </div>
        )
    }
} 

Edit: As of React 16.3 one should use getDerivedStateFromProps in this case.

Note that componentWillReceiveProps will be deprecated.

From the docs: getDerivedStateFromProps is invoked after a component is instantiated as well as when it receives new props. It should return an object to update state, or null to indicate that the new props do not require any state updates.

https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

like image 34
Ali Ankarali Avatar answered Oct 21 '22 13:10

Ali Ankarali


It is incorrect setting state in render method. You can set state in lifecyles method. But other thing is that your component can receive same props many times, so your component will be set state many times, and renderd. To solve this problem you need to compare your new with your current props for example compare json objects:

componentWillReceiveProps(nextProps) {
    if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
        this.setState({
            showButton: true,         
        })
    }
 }

or use PureComponent. And that garentee you that your component will not rerendered constantly.

And it will be better if you do not rerender component if state.showButton currently seted to true.

componentWillReceiveProps(nextProps) {
    if(JSON.stringify(this.props) !== JSON.stringify(nextProps) && nextProps.show) {
       if(!this.state.showButton) {
         this.setState({
            showButton: true,         
        })
       }

    }
 }
like image 1
Artem Mirchenko Avatar answered Oct 21 '22 14:10

Artem Mirchenko