Pablo Marino
Pablo Marino

Reputation: 491

react setstate not working when pushing elements to array

I am trying to push elements to an array of objects in the component state (in react.js), I am calling the method Array.concat which returns a new array.

pushDeck(id, name) {
  var newDeck = [{
    id: id,
    name: name
  }];
  console.log("new path should be: ", this.state.path.concat(newDeck));
  this.setState({
    path: this.state.path.concat(newDeck)
  }, () => {
    console.log("setstate callback: ", this.state.path);
  });
}

the first console.log prints the correct value for the path array, but after the callback for setstate is called, the second console.log prints an empty array. It's like this.setState were doing nothing

For more details: I call pushDeck from a grandChild component, I give the function pushDeck as a prop to the component DeckGallery and this one gives the function to one of its children. here is the entire main component:

import React, {Component} from "react";
import Page from "../components/page.jsx";
import Radium from "radium";
import {connect} from "react-redux";
import {getUserInfo} from "../actions/user";
import {successAlert} from "../actions/alerts";
import {fetchUserDecks, deleteUserDeck} from "../actions/deck.js";
import RaisedButton from 'material-ui/RaisedButton';
import CreateUserDeckContainer from "../containers/createUserDeckContainer.jsx";
import DeckGallery from "../components/deckGallery.jsx";
import _ from "lodash";

const style = {
    row1:{
        margin: "5px"
    },
    path:{
        color: "blue",
        cursor: "pointer"
    }
}


class Home extends Component{

    constructor(props){
        console.log("home constructor");
        super(props);
        this.state = {parentId:null, path:[]};
        this.fetchDecks = this.fetchDecks.bind(this);
        this.renderPath = this.renderPath.bind(this);
        this.goToIndex = this.goToIndex.bind(this);
        this.onDelete = this.onDelete.bind(this);
        this.pushDeck = this.pushDeck.bind(this);
    }

    componentWillMount(){
        console.log("will mount");
        this.props.getUserInfo();
    }

    fetchDecks(skip){
        console.log("fetch decks");
        this.props.fetchUserDecks(this.state.parentId, skip, this.state.path);
    }

    goToIndex(pathLastIndex){
        console.log("goto index", this.state.path);
        var limitToDrop = this.state.path.length - pathLastIndex;
        var newPath = _.dropRight(this.state.path, limitToDrop);
        this.setState({path: newPath});
    }

    pushDeck(id, name){
        var newDeck = [{id: id, name: name}];
        console.log("new path should be: ", Array.from(new Set(this.state.path.concat(newDeck))));
        this.setState({path: Array.from(new Set(this.state.path.concat(newDeck)))},
            ()=>{
            console.log("setstate callback: ", this.state.path);
        });
    }

    componentWillUpdate(nextProps, nextState){
        console.log("nextstate: ",  nextState);
    }

    renderPath(){
        return (
            <div>
                <span onClick={()=>this.goToIndex(0)} style={style.path}>Root</span>
                {this.state.path.map((p, i)=>{
                    <span key={(i+1)} onClick={()=>this.goToIndex(i+1)} style={style.path}>{p.name}</span>
                    })
                }
            </div>
        );
    }

    onDelete(deckId){
        console.log("on delete");
        this.props.deleteUserDeck(deckId, this.state.path, ()=>{
            this.props.successAlert("Deck deleted succesfully !");
            this.forceUpdate();
        });
    }

    render(){
        console.log("path at render: ", this.state.path);
        return (
            <Page name="my collection">
                <div className="container">
                    <div style={style.row1} className="row">
                        <div className="col-lg-9  col-sm-6">
                            <h2>Your decks</h2>
                             Path: {this.renderPath()}
                        </div>
                        <div className="col-lg-3 col-sm-6">
                            <CreateUserDeckContainer path={this.state.path}/>
                        </div>
                    </div>
                    <div className="row">
                        <div className="col">
                            <DeckGallery pushDeck={this.pushDeck} onDelete={this.onDelete} path={this.state.path} fetch={this.fetchDecks} decks={this.props.decks}/>
                        </div>
                    </div>
                </div>
            </Page>
        );
    }
}

function mapStateToProps(state){
    return {decks: state.userDecks};
}

export default connect(mapStateToProps, {getUserInfo, fetchUserDecks, deleteUserDeck, successAlert})(Radium(Home));

Update: I isolated the error to just this:

goToIndex(that){
        console.log("path at gotoindex: "+ JSON.stringify(that.state.path));
    }

    renderPath(){
        var that = this;
        console.log("path at renderpath: "+ JSON.stringify(that.state.path));   
        setTimeout(()=>{
            that.goToIndex(that);
        }, 0);
        that.goToIndex(that);
    }

When I call render this is what gets printed in the console:

path at renderpath: [{"id":"59cec39e3724bc137d935ed5","name":"math"}]
path at gotoindex: [{"id":"59cec39e3724bc137d935ed5","name":"math"}]
path at gotoindex: []

the last line is printed when goToIndex is called from inside setTimeout, it should print the same thing than when called outside setTimeout. also, I put a console.log in componentWillUpdate to see if the state was changing in the middle of both calls but it doesn't happen.

Upvotes: 3

Views: 2249

Answers (3)

GAJESH PANIGRAHI
GAJESH PANIGRAHI

Reputation: 1232

Try this:

pushDeck(id, name) {
  .....
  .....
  this.setState({
      path: [...new Set([...this.state.path, ...newDeck])],
      () => console.log("setstate callback: ", this.state.path);
   });
 ......
}

Upvotes: 0

Pablo Marino
Pablo Marino

Reputation: 491

The problem was that i was passing an array to the children, and since in Javascript arrays are passed by reference the children were modifying the array!,ie: the children were modifying the parent's state without it's supervision :p. the solution is to clone the array before passing it to children, I used the method slice() from the Array class for this

Upvotes: 0

xabitrigo
xabitrigo

Reputation: 1431

this is not bound to what you think on the callback.

Declaring your function with the arrow syntax will probably fix the problem.

Check this answer, I think it might help.

Upvotes: 0

Related Questions