Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ES6/React: Why is my triple-nested setState update not working?

I have a state object with three nest properties that I need to update only value at a time. So I use ES6 spread syntax to update the state, but for some reason it's given me undefined property type error whenever I run it.

When I only had two nested properties, it worked fine. What is the issue?

method(type, row, col) {
        this.setState({
            changedCells: {
                ...this.state.changedCells,
                [type]: {
                    ...this.state.changedCells[type],
                    [row]: {
                        ...this.state.changedCells[type][row],
                        [col]: true
                    }
                }
            }
        }
}

When the changedCells state is initially empty. And the setState method is like this, denoted with the asterisks, it runs fine. But in my first example with the cellState empty, and the type='wood', row=0, col=0, it does not work but works in the second example.

method(type, row, col) {
        this.setState({
            changedCells: {
                ...this.state.changedCells,
                [type]: {
                    ...this.state.changedCells[type],
                    [row]: {
                        ...this.state.changedCells[row], ***CHANGED***
                        [col]: true
                    }
                }
            }
        }
}
like image 373
SolidSnake Avatar asked Mar 16 '26 23:03

SolidSnake


1 Answers

Assuming your initial state is:

this.state = {
    changedCells: {}
};

Then your property accessing evaluates as follows:

this.state.changedCells evaluates to {}

this.state.changedCells[type] evaluates to undefined

this.state.changedCells[type][row] TypeError: Cannot read property row of undefined

Your code worked previously because you can use the spread operator on undefined:

{...undefined} === {}

You can solve your problem in two ways. Either initialise the state to contain every type and row it needs, e.g.

this.state = {
    changedCells: {
        typeA: {
            row1: {
                col1: false
            }
        }
    }
}

and so on. This is fine if you have a well defined set of types, rows and columns, but impractical if you have a lot of them or don't know their names up front.

The other option is to provide a default empty object when the object might be undefined:

method(type, row, col) {
    this.setState({
        changedCells: {
            ...this.state.changedCells,
            [type]: {
                ...this.state.changedCells[type],
                [row]: {
                    ...(this.state.changedCells[type] || {})[row],
                    [col]: true
                }
            }
        }
    }
}

There are some tools out there to make your life easier. You could use lodash get to retrieve the property, providing a default value.

method(type, row, col) {
    this.setState({
        changedCells: {
            ...(_.get(this.state, "changedCells", {})),
            [type]: {
                ...(_.get(this.state, ["changedCells", type], {})),
                [row]: {
                    ...(_.get(this.state, ["changedCells", type, row], {})),
                    [col]: true
                }
            }
        }
    }
}
like image 75
Alex Young Avatar answered Mar 19 '26 14:03

Alex Young



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!