0

This is my class Content which is rendered inside App.js. I have read through ReactJS tutorial and understand that I need to change data without mutation.

https://reactjs.org/tutorial/tutorial.html

But even when I use slice, the app does not re-render on state changes.

This class manage a deck of cards like below:

//omitted imports
import main_deck from ...
import side_deck from ...

export default class Content extends Component {
    constructor(props) {
        super(props);
        this.moveToMainDeck = this.moveToMainDeck.bind(this);
        this.state = {
            mainDeck : main_deck,
            sideDeck : side_deck
        }
    }

    moveToMainDeck(card) {
        const fromDeck = this.state.sideDeck.slice(); //copy the state data
        const toDeck = this.state.mainDeck.slice();

        var movable = checkMovability(fromDeck, toDeck, card);
        if (movable[0] === 1) { //use slice instead of splice
            var newMain = toDeck.slice(0, movable[1]).concat([card]).concat(toDeck.slice(movable[1]));

            var cardIndexInSideDeck = fromDeck.indexOf(card);
            var newSide = fromDeck.slice(0, cardIndexInSideDeck).concat(fromDeck.slice(cardIndexInSideDeck + 1));

            this.setState({ mainDeck : newMain, sideDeck : newSide });
        }
        else if (movable[0] === -1)
            alert("Unable to move. Main Deck is full.");
        else //movable[0] === 0
            alert("Unable to move. Limit reached for this card in Main Deck.");
    }

    render() {
        return (
            <div><MainDeck ... /><SideDeck ... /></div>
        );
    }
}

With the above code, I test the state data by printing out JSON array, the state does change after this.setState, but the view still does not re-render.

However, I tested by replacing slice() with push(), everything is re-rendered after state changes, like this: fromDeck.push(card); or toDeck.push(card);

Anyone please tell what is wrong with the slice I am using. Thanks a lot!

Jay Nguyen
  • 342
  • 1
  • 2
  • 18

2 Answers2

0

It's because slice function creates a shallow copy which means it only copies the structure of the object which further means that the fields of the object are not copied only the references are shared, so when you change in copy object the original object also changes, so when react compares the difference it finds none therefore it does not re-render.

//Example

var one=[{'a':'first'},{'b':'second'}]
var two=one.slice()
var three=two.slice(0,1)
three[0].a='third'

//Now in all variables (one,two,three) a will have value of 'three'
Mukesh Kumar
  • 944
  • 2
  • 10
  • 26
  • I get what you say, but in the code, I don’t actually modify ‘fromDeck’ and ‘toDeck’. Instead I declare 2 new vars to store the updated data. After ‘setState’, I print out the Json data to compare, it changes but view does not update. Can you suggest how to clone data to an entirely new adress in javascript? – Jay Nguyen May 22 '18 at 12:13
0

You could try using, as suggested here:

const fromDeck = JSON.parse(JSON.stringify(this.state.sideDeck))

This should create a completely new copy.

César Landesa
  • 2,626
  • 1
  • 13
  • 18
  • It won't work in my case, since a comment on that suggestion said it only works if the array contains only primitives, and no `null` or `infinite`. My `json` data has `null` and non-primitive objects. Thanks for your help anyways. – Jay Nguyen May 23 '18 at 06:40
  • 1
    In that case I would probably use [cloneDeep from lodash](https://lodash.com/docs/4.17.10#cloneDeep) so that I don't have to implement a custom function myself. – César Landesa May 23 '18 at 08:09