Tlmbg
Tlmbg

Reputation: 7

What does this error means? Render methods should be a pure function of props and state

I'm totally new in React and had stucked with this error.

Error occurs because of this method:

   deleteItem = (index) => {
    this.state.items.splice(index, 1),
    this.setState({
        items: this.state.items
    })
    }

I also using @babel/plugin-proposal-class-properties

If I change button onClick event to this:

     onClick={this.deleteItem.bind(index)}

it starts working perfectly and i dunno why.

Whole code example

import React from 'react';

export default class extends React.Component{
    constructor(props) {
    super(props)
    this.state = {
        items: [1, 2, 3, 4, 5]
    };
};
addItem = () => {
    this.state.items.push('added');
    this.setState({
        items: this.state.items
    });
}
deleteItem = (index) => {
    this.state.items.splice(index, 1),
    this.setState({
        items: this.state.items
    })
}
render(){
    const list = this.state.items.map((item, index) => {
        return <li key={index}>{item}
        <button onClick={this.deleteItem(index)}>Remove</button>
        </li>
    });
    return (
        <div>
            <ul>
                {list}
            </ul>
            <button onClick={this.addItem}>Add</button>
        </div>
    )
}
}

How can i fix this?

Upvotes: 0

Views: 2112

Answers (4)

Miro Lehtonen
Miro Lehtonen

Reputation: 649

The error message refers to a common concept in functional programming - pure functions - which helps you write less buggy code. Pure functions have no side effects in that they don't assign new values to variables, and they don't mutate the state of the application. A famous example of a pure function is one that returns the square root of the argument. There's a test you can run in your mind: can you call the function multiple times with the same result/outcome? If it's a pure function, calling it multiple times does not change the result.

In your example, rendering the same content multiple times should be possible without any side effects. Rendering the content should not add or delete any items. If you want this pure function (render) to return a different result, you have to call it with different arguments which are the props and state.

Upvotes: 1

Fullstack Guy
Fullstack Guy

Reputation: 16908

As per the React docs:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

You are directly mutating the state, don't do it. Pass a new state object in the setState() method.

 deleteItem = (index) => {
    const newItems = this.state.items.filter((ele, idx) => idx != index),
    this.setState({
        items: newItems
    })
 }

Or you can use the Array.prototype.slice() function, which does not modify the original array as it takes a copy of it in the given range and returns it in a new array:

deleteItem = (index) => {
    const newItems = [...s.slice(0, index), ...s.slice(index + 1)],
    this.setState({
        items: newItems
    })
 }

Also the this context is not set in your onClick handler, use a arrow function () => to capture the lexical this:

onClick={() => this.deleteItem(index)}

That is necessary as this points to the current execution context, if you don't bind this to the lexical this and when the callback is invoked the this won't be referring to your component instance anymore.

Also if you invoke deleteItem(index) immediately the handler will point to undefined instead of the deleteItem reference as the deleteItem(index) returns undefined.

You need to modify the addItem() method as well, where you should form a new array from the added element:

addItem = () => {
    this.setState({
        items: this.state.items.concat('added')
    });
}

Upvotes: 1

akshay bagade
akshay bagade

Reputation: 1219

We must not mutate states directly

Instead of

addItem = () => {
    this.state.items.push('added');
    this.setState({
        items: this.state.items
    });
}

Use

addItem = () => {
    this.setState({
        items: […this.state.items,'added']
    });
}

And Instead of

deleteItem = (index) => {
    this.state.items.splice(index, 1),
    this.setState({
        items: this.state.items
    })
}

Use

deleteItem = (index) => {
  this.setState(state => ({
    items: state.items.filter((item, idx) => index !== idx)
  }))
}

Use latest es6 or latter features of javascript as arrow function, object destructuring, spread operator and other

Upvotes: 0

Clarity
Clarity

Reputation: 10873

The problem is that you're mutating your state directly, use filter instead:

deleteItem = (index) => {
  this.setState(state => ({
    items: state.items.filter((item, i) => index !== i)
  }))
}

I've recently wrote a post about this and other issues when using React.

Also looking at your render code, when you do <button onClick={this.deleteItem(index)}>Remove</button>, it will execute deleteItem immediately and pass the result of that to onClick, however onClick expects a function. What you need to do is to wrap it in a function:

<button onClick={() => this.deleteItem(index)}>Remove</button>

Upvotes: 0

Related Questions