3

I'm trying to update my state object which contains multiple levels. See this example https://codesandbox.io/s/oq9kp9vy5y.

The problem is, that the values in the render method, does not update ... only the first one. Can I force an update or is there a better way?

Thanks Kasper

Zach
  • 539
  • 1
  • 4
  • 22
Kasper Gantzhorn
  • 201
  • 4
  • 12
  • You should include relevant code snippets as opposed to just a link, as the link could break. – Galupuf Jan 18 '19 at 19:12
  • Why are you setting state in `componentDidMount()`? Why not just have it be your initial state? – Galupuf Jan 18 '19 at 19:13
  • You are updating one value (state.kitchen) and rendering another (state.calcuatedUsage.kitchen). – XCS Jan 18 '19 at 19:13
  • I actually find the codesandbox link very helpful. It made it much easier for me to help debug. – Shane Cavaliere Jan 18 '19 at 19:20
  • 1
    @ShaneCavaliere "If it is possible to create a live example of the problem that you can link to (for example, on http://sqlfiddle.com/ or http://jsbin.com/) then do so - but also include the code in your question itself. Not everyone can access external sites, and the links may break over time." - https://stackoverflow.com/help/how-to-ask – Galupuf Jan 18 '19 at 19:23
  • Fair enough, good point :) – Shane Cavaliere Jan 18 '19 at 19:24
  • @Galupuf, next time I'll include relevant code, thanks for the guidelines. The reason why I'm updating my state object in componentDidMount() is only to give a simple example. – Kasper Gantzhorn Jan 18 '19 at 21:09
  • @ShaneCavaliere, thanks I agree with you:) – Kasper Gantzhorn Jan 18 '19 at 21:09

3 Answers3

6

In your initial state, you have the kitchen object inside the calculatedUsage object. But in your setState call, you have the kitchen object outside of calculatedUsage.

Also, when you are accessing the previous state within setState, it's best to use the functional version of setState, e.g.:

componentDidMount() {
  this.setState(
    prevState => ({
      caclulatedUsage: {
        ...prevState.caclulatedUsage,
        bath: 12,
        kitchen: {
          ...prevState.caclulatedUsage.kitchen,
          stove: 14,
          fridge: 23,
          dishwasher: 34
        }
      }
    }),
    () => {
      console.log(this.state);
    }
  );
}

The reason for this is that setState can be asynchronous, which means that accessing this.state within it may not give you the value you expect. Using the functional version for transactional state changes guarantees consistent results.

You may also want to take a look at the immer library. It makes updating deeply nested state much easier, by allowing you to do immutable updates with a mutable API. Here's a fork of your codesandbox example that uses immer: https://codesandbox.io/s/180p7p0o84

Shane Cavaliere
  • 2,175
  • 1
  • 17
  • 18
2

Try something like this

import React, { Component } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      caclulatedUsage: {
        bath: 0,
        kitchen: {
          stove: 0,
          fridge: 0,
          dishwasher: 0
        },
        livingroom: {
          tv: 0,
          tvBox: 0
        }
      }
    };
  }

  componentDidMount() {
    this.setState(
      {
        caclulatedUsage: Object.assign({}, this.state.access, {
          caclulatedUsage: {
            ...this.state.caclulatedUsage,
            bath: 12
          },
          kitchen: {
            ...this.state.caclulatedUsage.kitchen,
            stove: 14,
            fridge: 23,
            dishwasher: 34
          }
        })
      },
      () => {
        console.log(this.state);
      }
    );
  }

  render() {
    return (
      <div className="App">
        <h1>Hello CodeSandbox</h1>
        {this.state.caclulatedUsage.bath}
        <br />
        {this.state.caclulatedUsage.kitchen.stove}
        <br />
        {this.state.caclulatedUsage.kitchen.fridge}
        <br />
        {this.state.caclulatedUsage.kitchen.dishwasher}
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
TRomesh
  • 4,323
  • 8
  • 44
  • 74
  • This should be the correct Never mutate this.state . Treat this.state as if it were immutable. But whats the point of setting state in componentDidMount() ? – Madushika Perera Jan 18 '19 at 19:16
  • This example accesses `this.state` inside of the `this.setState` call, which is not a good idea. If you need to access the previous state while updating, you should use the functional version of `this.setState`. – Shane Cavaliere Jan 18 '19 at 19:22
0

Try:

  componentDidMount() {
    this.setState(
      {
        caclulatedUsage: {
          ...this.state.caclulatedUsage,
          bath: 12,
           kitchen: {
               ...this.state.caclulatedUsage.kitchen,
               stove: 14,
               fridge: 23,
               dishwasher: 34
           }
        }

      }
      () => {
        console.log(this.state);
      }
    );
  }
SomoKRoceS
  • 2,934
  • 2
  • 19
  • 30
  • This example accesses `this.state` inside of the `this.setState` call, which is not a good idea. If you need to access the previous state while updating, you should use the functional version of `this.setState`. – Shane Cavaliere Jan 18 '19 at 19:24