Search code examples
reactjsreact-hooksreact-state-management

React state comparison with objects and nested arrays


I am trying to compare state against a previous version of the same object (useRef) to detect for changes.

The object that loads into state with useEffect looks like this:

{
  active: true,
  design: [
    {
      existing: '55Xgm53McFg1Bzr3qZha',
      id: '38fca',
      options: ['John Smith', 'Jane Doe'],
      required: true,
      title: 'Select your name',
      type: 'Selection List'
    },
   {
      existing: '55Xgm53McFg1Bzr3qZha',
      id: '38fca',
      options: ['John Smith', 'Jane Doe'],
      required: true,
      title: 'Select your name',
      type: 'Selection List'
    },
  ],
  icon: '🚜',
  id: '92yIIZxdFYoWk3FMM0vI',
  projects: false,
  status: true,
  title: 'Prestarts'
}


This is how I load in my app object and define a comparitor state (previousApp) to compare it and detect changes (with lodash) this all works great until I change a value in the design array.

const [app, setApp] = useState(null)
const [edited, setEdited] = useState(false)
const previousApp = useRef(null)

 useEffect(() => {
    if (app === null) {
      getApp(someAppId).then(setApp).catch(window.alert)
    }
    if (previousApp.current === null && app) {
      previousApp.current = { ...app }
    }
    if (previousApp.current && app) {
      _.isEqual(app, previousApp.current) ? setEdited(false) : setEdited(true)
    }
  }, [app])

For example I change the input value of app.design[0].title = 'testing' with the following code:

const updateItem = e => {
  let newApp = { ...app }
  let { name, value } = e.target
  newApp.design[0][name] = value
  setApp(newApp)
}

This works in the sense that it updates my app state object but not that it detects any changes in comparison to previousApp.current

When I console log app and previousApp after changing both values, they are identical. Its seems to update my previousApp value aswell for some reason.


Rearranging the design array works as expected and detects a change with the following function:

const updateDesign = e => {
  let newApp = { ...app }
  newApp.design = e
  setApp(newApp)
}

Solution

  • It probably wasn't detecting any difference because the array present in the design property was keeping the same reference.

    When you do this:

    let newApp = { ...app }
    

    You're creating a new object for your app, but the property values of that object will remain the same, because it's a shallow copy. Thus, the design array (which is an object, and therefore is handled by reference) will keep its value (the reference) unaltered.

    I think this would also solve your problem:

    const updateItem = e => {
      let newApp = { ...app };
      let newDesign = Array.from(app.design);  // THIS CREATES A NEW ARRAY REFERENCE FOR THE 'design' PROPERTY
      let { name, value } = e.target;
      newDesign[0][name] = value;
      setApp({
        ...newApp,
        design: newDesign
      });
    }