1

I'm currently trying to take two objects of objects, where the second one has updated values, and merge the updated values into the first one. I wrote a function to do this but i'm unable to update the values within my AnimatedDataWrapper. However if I run it outside of the AnimatedDataWrapper it works fine..

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import * as d3 from 'd3'

const mapNewStateToOldState = (oldState, newState) => {
  Object.keys(oldState).forEach((key) => {
    Object.assign(oldState[key], newState[key])
  })
  return oldState
}

// const mapNewStateToOldState = (oldState, newState) =>
//  Object.keys(oldState).map(key => Object.assign(oldState[key], newState[key]))

const obj = { 0: { data: 1 } }
const newObj = { 0: { data: 2 } }

console.log(mapNewStateToOldState(obj, newObj)) // THIS WORKS
console.log(obj) // THIS WORKS

const AnimatedDataWrapper = (dataProp, transitionDuration = 300) => ComposedComponent =>
  class extends Component {
    constructor(props) {
      super(props)
      const data = this.props[dataProp]
      this.state = Object.keys(data)
        .map(label => ({ [label]: data[label] }))
        .reduce((prev, curr) => ({ ...prev, ...curr }), {})
    }

    componentWillReceiveProps(nextProps) {
      const data = this.props[dataProp]
      console.log(data)
      const nextData = nextProps[dataProp]
      const dataKeys = this.props.dataKeys
      const dataUnchanged = Object.keys(data)
        .map(label => data[label] === nextData[label])
        .reduce((prev, curr) => prev && curr)
      if (dataUnchanged) {
        return
      }
      d3.select(this).transition().tween('attr.scale', null)
      d3
        .select(this)
        .transition()
        .duration(transitionDuration)
        .ease(d3.easeLinear)
        .tween('attr.scale', () => {
          const barInterpolators = data.map((...args) => {
            const index = args[1]
            return dataKeys.map((key) => {
              const interpolator = d3.interpolateNumber(
                this.state[index][key],
                nextData[index][key],
              )
              return { key, interpolator }
            })
          })
          return (t) => {
            const newState = barInterpolators
              .map(bar =>
                bar
                  .map(({ key, interpolator }) => ({ [key]: interpolator(t) }))
                  .reduce((result, currentObject) => {
                    Object.keys(currentObject).map((key) => {
                      if (Object.prototype.hasOwnProperty.call(currentObject, key)) {
                        result[key] = currentObject[key]
                      }
                      return null
                    })
                    return result
                  }, {}),
              )
              .reduce((newObject, value, index) => {
                newObject[index] = value
                return newObject
              }, {})
            const oldState = this.state
            console.log(`OLD STATE = ${JSON.stringify(oldState)}`)
            console.log(`NEW STATE = ${JSON.stringify(newState)}`)
            const updatedState = mapNewStateToOldState(oldState, newState) // THIS DOES NOT WORK
            console.log(`UPDATED STATE = ${JSON.stringify(updatedState)}`)
            this.setState(updatedState)
          }
        })
    }

    render() {
      const { props, state } = this
      const newData = Object.keys(state).map(val => state[val])
      const newDataProps = { ...{ data: newData } }
      const newProps = { ...props, ...newDataProps }
      return <ComposedComponent {...newProps} />
    }
  }

AnimatedDataWrapper.PropType = {
  dataProp: PropTypes.string.isRequired,
  transitionDuration: PropTypes.number,
  dataKeys: PropTypes.instanceOf(Array).isRequired,
  maxSurf: PropTypes.number.isRequired,
}

export default AnimatedDataWrapper

Here is what the objects i'm passing into the function mapNewStateToOldState (oldState, newState) look like. And what the output updatedState looks like.

enter image description here It seems like maybe it would be a scoping issue? But i can't seem to figure out what is going on. I tried manually merging it with no luck either.

6
  • Could it be that your new state has keys that are not present in oldState? It would anyway make more sense to say: Object.keys(newState).forEach instead of Object.keys(oldState).forEach, or if you want to remove keys from oldState if they no longer exist, then [...Object.keys(oldState), ...Object.keys(newState)].forEach, maybe converted to a Set to avoid duplicate actions. Commented Jun 19, 2017 at 6:28
  • No the newState will contains two keys that I'm sure are also present in the oldState. I attached an image of what the objects look like in dev tools. What I found weird is that once i was inside the loop Object.keys(oldState).forEach((key) => { if I manually accessed the keys of both objects like: oldState[key].aggSurfMin and newState[key].aggSurfMin they printed the same value, even though in the dev tools they have different values.. Commented Jun 19, 2017 at 16:44
  • 1
    Does your console.log in componentWillReceiveProps() correspond to the object under 'updatedState=' in your console screenshot? If so, this is because you are not logging new state, you are logging the old props which at that time are unchanged and equal to the initial props. If possible, please update your code with the version where you also log 'OLD STATE=', 'NEW STATE=' and 'updatedState='. Commented Jun 19, 2017 at 19:57
  • 1
    Also, don't rely on devtools to know what an objects value is at the moment it was logged, because the devtools retrieve the logged object's contents asynchronously, showing what might be the result of later mutation. To be sure what the value is at the moment of logging, stringify the object with JSON.stringify Commented Jun 19, 2017 at 19:58
  • @MilošRašić I just updated the code with the console.logs but added the JSON stringify to them as trincot suggested. I'm busy with other stuff but after a quick glance it looks like you are right. I think i'm updating the wrong object and i couldn't tell because using console.log to print the object was giving me incorrect information. Commented Jun 19, 2017 at 20:45

1 Answer 1

2

Good ol' Object.assign will do the job you're looking for, where preceding objects will be overwritten by others that follow with the same keys:

var oldState = {a: 1, b: 2}
var newState = {b: 3, c: 4}
Object.assign(oldState, newState) === { a: 1, b: 3, c: 4 }

In stage-3 ecmascript you can use the spread syntax:

var oldState = {a: 1, b: 2}
var newState = {b: 3, c: 4}
{ ...oldState, ...newState } === { a: 1, b: 3, c: 4 }
Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for the reply. I'm actually already using Object.assign here const mapNewStateToOldState = (oldState, newState) => { Object.keys(oldState).forEach((key) => { Object.assign(oldState[key], newState[key]) }) return oldState } but for some reason when i call it here const updatedState = mapNewStateToOldState(oldState, newState) it won't work.
Object.assign returns a new object. So in each loop in mapNewStateToOldState a new object not being assigned to any variable so it's being lost. Then when you return the old state you're really just returning the previous version
Make this line in your code to this: const updatedState = Object.assign(oldState, newState)
I tried the above but it doesn't work, since the objects are nested objects. I need the keep the non-updated values in the oldState and only update the values contain in the nested object of the newState. Also doesn't object.assign also change the target object?
@KeithAlpichi, that is misinformation: Object.assign mutates the first argument.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.