title: React setState not merging all changes
body: <h2>EDIT - I can't vote for my own answer at the moment but this turned out to be a reference/immutability issue - see <a href="https://stackoverflow.com/a/60189228/11602055">https://stackoverflow.com/a/60189228/11602055</a></h2>
  
  <p>this one has me completely stumped!</p>
  
  <p>I'm getting some data from my server and applying it to my state, but some of the properties I'm applying to the state don't migrate to the state after setState has completed...</p>
  
  <p>I've tried everything I can think of regarding this, I'm reading the final state as part of the callback from setState to ensure I'm getting the state after setState has completed so it's nothing to do with setState being Async in nature.</p>
  
  <p>Have attempted the following -</p>
  
  <ul>
  <li>use a blank object, save new props to that object and then return that.</li>
  <li>apply each property one at a time in separate setState calls inside an outer loop on the keys</li>
  <li>return Object.assign({},this.state,data);</li>
  <li>return {...data};</li>
  <li>return {...this.state,...data};</li>
  <li>await setState(...);</li>
  <li>setState(data)</li>
  </ul>
  
  <p>I've got a couple of nested state properties but this is affecting root properties of the state object...</p>
  
  <p>See code/output/end state below, note for example that eventColour has not propagated through the setState action to the final state... (same applies for iconCls...)</p>
  
  <p>Help please?...</p>
  
  <p>Code:</p>
  
  <pre><code>getJobDetail = async function (){
  
      // get data from server
      let response = await window.fetch("/event/events?id="+this.props.job,{cache: "no-cache", credentials: "same-origin"});
  
      // parse returned data
      let data = await response.json();
  
      // couple of minor data manipulation exercises
      if (data.tempToFeedback!==null){
          data.toFeedback=data.tempToFeedback;
      }
      if (data.tempSwitchNotes!==null){
          data.switchingNotes=data.tempSwitchNotes;
      }
      if (data.editedName!==null){
          data.name=data.editedName;
      }
      if (data.fileList!==null){
          data.fileList=JSON.parse(data.fileList);
      }
  
      // merge all recieved data onto state
      this.setState(currentState=&gt;{
  
          for (let key of Object.keys(data)){
              console.log("setting " + key + " from "+currentState[key]+" to " + data[key]);
              currentState[key]=data[key];
          }
  
          return currentState;
      }
      ,
          ()=&gt;{
              // output new state on callback (doesn't include everything which was merged in...)
              console.log(JSON.parse(JSON.stringify(this.state)))
          }
      )
  
  }
  </code></pre>
  
  <p>Console output during setState</p>
  
  <pre><code>setting id from undefined to 214793
  
  
  setting tnccJobsID from undefined to 214793
   setting startDate from undefined to 2020-02-12T08:00:00.000Z
   setting endDate from undefined to 2020-02-12T09:00:00.000Z
   setting resourceId from undefined to 99921
   setting FKusers from undefined to 99999
   setting previousJob from undefined to null
   setting isInfoBooking from undefined to 0
   setting isCSP from undefined to null
   setting isDAR from undefined to 0
   setting hasStarted from undefined to 0
   setting hasEnded from undefined to 0
   setting isCancelled from undefined to 0
   setting FKtogaGroups from undefined to 12327
   setting currentStep from undefined to 0
   setting adjustedFKjobTypes from undefined to null
   setting FKjobTypes from undefined to 23
   setting lastUpdated from undefined to null
   setting name from undefined to undefined
   setting jobTypeID from undefined to 23
   setting eventType from undefined to Operational Release
   setting eventColor from undefined to orange
   setting eventStyle from undefined to colored
   setting iconCls from undefined to fas fa-plane-departure
   setting weight from undefined to 1
   setting northDesk from undefined to 1
   setting southDesk from undefined to 0
   setting switchOutState from undefined to 1
   setting feedbackState from undefined to 1
   setting feedbackRequired from undefined to 0
   setting demandAtRisk from undefined to null
   setting demandAtRiskApproved from undefined to null
   setting runwaySteps from undefined to [object Object],[object Object],[object Object],[object Object],[object Object]
   setting startStep from undefined to 4
   setting endStep from undefined to 5
   setting fileList from undefined to 
   setting outageList from undefined to [object Object]
   setting toFeedback from undefined to undefined
   setting switchingNotes from undefined to undefined
  </code></pre>
  
  <p>Final state as read out in callback</p>
  
  <pre><code>updateData: {}
  fileDrawer: null
  fileUploading: null
  showJobDetailModal: false
  id: 214793
  tnccJobsID: 214793
  startDate: "2020-02-12T08:00:00.000Z"
  endDate: "2020-02-12T09:00:00.000Z"
  resourceId: 99921
  FKusers: 99999
  previousJob: null
  isInfoBooking: 0
  isCSP: null
  isDAR: 0
  hasStarted: 0
  hasEnded: 0
  isCancelled: 0
  FKtogaGroups: 12327
  currentStep: 0
  adjustedFKjobTypes: null
  FKjobTypes: 23
  lastUpdated: null
  jobTypeID: 23
  eventType: "Operational Release"
  eventStyle: "colored"
  weight: 1
  northDesk: 1
  southDesk: 0
  switchOutState: 1
  feedbackState: 1
  feedbackRequired: 0
  demandAtRisk: null
  demandAtRiskApproved: null
  runwaySteps: (5) [{…}, {…}, {…}, {…}, {…}]
  startStep: 4
  endStep: 5
  fileList: []
  outageList: [{…}]
  __proto__: Object
  </code></pre>
  
  <p>Image formats as well!</p>
  
  <p><a href="https://i.stack.imgur.com/Lf8wH.png" rel="nofollow noreferrer">Code</a></p>
  
  <p><a href="https://i.stack.imgur.com/LmXzM.png" rel="nofollow noreferrer">setState Output</a></p>
  
  <p><a href="https://i.stack.imgur.com/9fvAX.png" rel="nofollow noreferrer">state as read in callback</a></p>
  
  <p>Demo version of code with data in it:</p>
  
  <pre><code>    import React, { Component } from 'react';
  
  
  export const JobView = class JobView extends React.PureComponent{
  
  
  
      render(){
  
  
              return null;
  
      }
  
  }
  
  
  export const JobDetails = class JobDetails extends React.Component{
      constructor(props){
          super(props);
          this.state={
              // jobData:null,
              updateData:{},
  
  
          }
  
  
          this.getJobDetail = this.getJobDetail.bind(this);
  
          this.setState = this.setState.bind(this);
  
  
      }
  
      componentDidMount(){
  
  
  
  
  
          this.getJobDetail();
  
  
          }
  
  
      getJobDetail = async function (){
  
          // get data from server
          // let response = await window.fetch("/event/events?id="+this.props.job,{cache: "no-cache", credentials: "same-origin"});
  
          // parse returned data
          // let data = await response.json();
          let data = JSON.parse('{"id":5267,"tnccJobsID":5267,"startDate":"2020-02-12T08:00:00.000Z","endDate":"2020-02-12T09:00:00.000Z","resourceId":99921,"FKusers":99999,"previousJob":null,"isInfoBooking":0,"isCSP":null,"isDAR":0,"hasStarted":null,"hasEnded":null,"isCancelled":null,"FKtogaGroups":2830,"currentStep":1,"adjustedFKjobTypes":null,"FKjobTypes":1,"lastUpdated":"2020-01-21T22:30:43.000Z","name":"OCKER HILL 132KV BUS SECTION 120 .","jobTypeID":1,"eventType":"Operational Release","eventColor":"orange","eventStyle":"colored","iconCls":"fas fa-plane-departure","weight":1,"northDesk":1,"southDesk":0,"switchOutState":1,"feedbackState":1,"feedbackRequired":0,"demandAtRisk":null,"demandAtRiskApproved":null,"runwaySteps":[{"step":1,"active":{"icon":"loading","text":"Planning"},"isLast":false,"completed":{"icon":"bars","text":"Planned"}},{"step":2,"active":{"icon":"loading","text":"Negotiating TSC"},"isLast":false,"completed":{"icon":"file-done","text":"TSC Recieved"}},{"step":3,"active":{"icon":"loading","text":"Awaiting Allocation"},"isLast":false,"completed":{"icon":"user","text":"Allocated"}},{"step":4,"active":{"icon":"loading","text":"Waiting to Start"},"isLast":false,"completed":{"icon":"user","text":"Started"}},{"step":5,"active":{"icon":"loading","text":"Switching In Progress"},"isLast":false,"completed":{"icon":"api","text":"Switched"}},{"step":6,"active":{"icon":"loading","text":"Releasing To Safety"},"isLast":true,"completed":{"icon":"check","text":"Released To Safety"}}],"startStep":4,"endStep":6,"fullHistory":[{"time":"2019-11-23T22:58:44.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasEnded Changed"},{"time":"2019-11-23T22:58:44.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasStarted Changed"},{"time":"2019-11-23T22:58:44.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"isCancelled Changed"},{"time":"2019-11-25T23:03:12.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasEnded Changed"},{"time":"2019-11-25T23:03:12.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasStarted Changed"},{"time":"2019-11-25T23:03:12.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"isCancelled Changed"},{"time":"2019-11-28T22:59:03.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasEnded Changed"},{"time":"2019-11-28T22:59:03.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"hasStarted Changed"},{"time":"2019-11-28T22:59:03.000Z","entryTo":"No","entryFrom":0,"changedBy":"System","message":"isCancelled Changed"},{"time":"2020-01-13T22:39:10.000Z","entryTo":0,"entryFrom":null,"changedBy":"System","message":"isDAR Changed"}],"fileList":"[]","outageList":[]}');
  
          // console.log(JSON.stringify(data));
          // couple of minor data manipulation exercises
          if (data.tempToFeedback!==null){
              data.toFeedback=data.tempToFeedback;
          }
          if (data.tempSwitchNotes!==null){
              data.switchingNotes=data.tempSwitchNotes;
          }
          if (data.editedName!==null){
              data.name=data.editedName;
          }
          if (data.fileList!==null){
              data.fileList=JSON.parse(data.fileList);
          }
  
          // merge all recieved data onto state
          await this.setState((currentState,props)=&gt;{
              let newState = {}
              for (let key of Object.keys(data)){
                  console.log("setting " + key + " from "+currentState[key]+" to " + data[key]);
                  newState[key]=data[key];
              }
              console.log(newState);
              return newState;
          }
  
  
          ,
              ()=&gt;{
                  // output new state on callback (doesn't include everything which was merged in...)
  
                  console.log(this.state);
              }
          )
  
      }
  
  
  
  
  
      render(){
          return JSON.stringify(this.state);
      }
  
  }
  </code></pre>