3

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.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Pablo Marino
  • 471
  • 1
  • 5
  • 12
  • i think this is not the right way to check if you states has made the changes you wanted. the state you are referring here might just be the old state – Rei Dien Oct 02 '17 at 01:12
  • @ReiDien but even if console.log(nextState) in the method componentWillUpdate where nextState is the second param the function receives it shows path as an empty array :( – Pablo Marino Oct 02 '17 at 01:28
  • try this ```path: Array.from(new Set(this.state.path.concat(newDeck)))``` – Rei Dien Oct 02 '17 at 01:33
  • @ReiDien it works exactly the same as the old way – Pablo Marino Oct 02 '17 at 01:36
  • You should be seeing state returned accurately there. Could you post your component code where you call pushDeck from? – James Gentes Oct 02 '17 at 01:39
  • yea you can say so, but i have problems with replacing objects in the past using that syntax - concat, and this syntax can work a miracle. – Rei Dien Oct 02 '17 at 01:41
  • @JamesGentes I edited the question for adding the entire component! – Pablo Marino Oct 02 '17 at 01:49
  • I think the way you are referencing the function in the child component is the cause of the issue.. take a look at how it's done here: https://stackoverflow.com/questions/35537229/how-to-update-parents-state-in-react – James Gentes Oct 02 '17 at 02:34
  • @JamesGentes Thanks, but i think that's exactly what i'm doing, i mean when you trigger an event in the grandchild of the Home component it calls the function pushDeck with the right parameters for updating the state in the Home, React even re renders the component, but without updating the state. it does however update the state if i just do somehing like: this.setState({path:[3]}) but if i do this.setState({path:[number:3]}) (inside the pushDeck function obviously)the same error happens – Pablo Marino Oct 02 '17 at 03:19

3 Answers3

0

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.

xabitrigo
  • 1,341
  • 11
  • 24
0

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

Pablo Marino
  • 471
  • 1
  • 5
  • 12
0

Try this:

pushDeck(id, name) {
  .....
  .....
  this.setState({
      path: [...new Set([...this.state.path, ...newDeck])],
      () => console.log("setstate callback: ", this.state.path);
   });
 ......
}
GAJESH PANIGRAHI
  • 1,204
  • 10
  • 17