Search code examples
javascriptreactjsfunctional-programmingpure-function

React, pure function warning?


I am trying to learn react and functional programming by trying to implement a simple todo app. I am not using flux as I am just trying to see the concepts of passing information between parent and children. I am trying to trigger a function in the parent on a click event in the child. However, I get a very nasty error from React about using pure functions and state. Can someone explain what I am doing wrong and what may be the right way of doing things? What is impure about my functions, I don't see the side-effects I am creating. Here is my code:

var React = require('react');
var Todo = require('./Todo');

const todos = [ {task: "Eat", completed: false},
            {task: "Breathe", completed: false},
            {task: "Sleep", completed: false}];

var TodoList = React.createClass({
    getInitialState: function() {
        return {todos: todos};
    },
    changeTodoStatus (task) {
        var updatedTodos = this.state.todos.map(function(todo){
            if (task.task === todo.task) {
                return {task: todo.task, completed: !todo.completed};
            } else {
                return todo;
            }
        });
        this.setState({todos: updatedTodos});
    },
    render: function() {
        var _that = this;
        return(
            <div className="container">
                <div className="row list-of-things">
                    <ul className="list-group">
                        {
                          this.state.todos.map( (todo, index) => {
                                return (<Todo clickHandler={ this.changeTodoStatus } key={index} todo={todo} />);
                            })
                        }
                    </ul>
                </div>
            </div>
        );
    }
});

module.exports = TodoList;

var Todo = React.createClass({
    handleClick( todo ){
        this.props.clickHandler( todo );
    },
    render: function() {
        if( this.props.todo.completed === true){
            return ( <li onClick={ this.handleClick(this.props.todo.task) } className="list-group-item list-group-item-success"><strike>{this.props.todo.task}</strike></li> );
        } else {
            return ( <li onClick={ this.handleClick(this.props.todo.task) } className="list-group-item"> {this.props.todo.task} </li> );
        }
    }
});

module.exports = Todo;

Any help/clarification is greatly appreciated!

This is the error: bundle.js:9139 Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.


Solution

  • in your onclick handlers for todo, you are actually calling those functions instead of referencing. What i would do is inside of your Todo component:

    handleClick() {
      this.props.clickHandler( this.props.todo );
    {
    

    and in your render just do

    render: function() {
        if( this.props.todo.completed === true){
            return ( <li onClick={ this.handleClick } className="list-group-item list-group-item-success"><strike>{this.props.todo.task}</strike></li> );
        } else {
            return ( <li onClick={ this.handleClick } className="list-group-item"> {this.props.todo.task} </li> );
        }
    }
    

    the way you have it now you are actually calling this.handleClick(this.props.todo) so as soon as the component renders it calls that function which is setting state immediately in the parent component which is against the react pattern AKA you don't set state in the render method