bradrar
bradrar

Reputation: 737

Using a function as a value to change the state in ReactJS

I am trying to add functionality to the buttons on my app's list so that when UP is clicked the list item swaps with the item directly on top of it.

I tried using a function as a value of my state in setState. But when I click the button this error occurred :

TypeError: Cannot read property 'map' of undefined
App.render
src/App.js:49
  46 | return( 
  47 |   <div>
  48 |     <h1>UNUM Challenge</h1>
> 49 |     <ol>
     | ^  50 |       {this.state.shoppingList.map((item, index) =>
  51 |          (<li data-index = {index} key={index}>
  52 |             {item}

What is happening here? Can't I use a function as the value when setting my state like this?

this.setState({
        shoppingList: arraymove(shoppingList, currentIndex , currentIndex - 1) 
}) 

Here is the full code:

import React, { Component } from 'react';    

function arraymove(arr, fromIndex, toIndex) {
        var element = arr[fromIndex];
        arr.splice(fromIndex, 1);
        arr.splice(toIndex, 0, element);
    }

    class App extends React.Component {
        constructor(props){
        super(props);

        this._handleItemSort = this._handleItemSort.bind(this);

        this.state = {
            shoppingList: ['Bananas', 'Apples', 'Rice', 'Eggs' , 'GTX 1080Ti', 'Avocado']
        }
      }



      _handleItemSort(dir, currentIndex) {
        // create new list of items from a spread of our current shoppingList state.
        // we don't want to reference the actual state and mutate it! 😱
        const shoppingList = [...this.state.shoppingList]

        if (dir === "up" ){
          this.setState({
            shoppingList: arraymove(shoppingList, currentIndex , currentIndex - 1) 
          }) 
        }

      }

      render() {
        return( 
          <div>
            <h1>UNUM Challenge</h1>
            <ol>
              {this.state.shoppingList.map((item, index) =>
                    (<li data-index = {index} key={index}>
                    {item}
                    <div className='controls'>
                      <button 
                        disabled={index === 0} 
                        onClick={ () => this._handleItemSort("up", index) }>UP</button>
                      <button
                        disabled={this.state.shoppingList.length === index + 1}
                        onClick={ () => this._handleItemSort("down", index) } >DOWN</button>
                    </div>
                  </li>)
                )}
            </ol>
          </div>
        );
      }
    }

Upvotes: 2

Views: 1640

Answers (2)

Cyril Gandon
Cyril Gandon

Reputation: 17048

arraymove doesn't returns, it mutates the state of the list, and return undefined.

So the call

shoppingList: arraymove(shoppingList, currentIndex , currentIndex - 1) 

is equivalent to

shoppingList: undefined

So the fix is minimal:

const shoppingList = [...this.state.shoppingList];
arraymove(shoppingList , currentIndex , currentIndex - 1) 
this.setState({
    shoppingList: shoppingList 
}) 

Upvotes: 1

AkshayM
AkshayM

Reputation: 1441

Returning arr.splice() will give you an array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned.

You need to return modified array like this:

function arraymove(arr, fromIndex, toIndex) {
  var element = arr[fromIndex];
  arr.splice(fromIndex, 1);
  arr.splice(toIndex, 0, element);
  return arr;
}

Upvotes: 2

Related Questions