reactjsobservablemobxmobx-react

MobX React - Updating a single index of an array of objects is rerendering all children


I have a mobx store of Objects that are being displayed in a list view with a map on the store value like so:

{ObjectStore.objects.map((obj, index) =>      
    <ObjectItem
       key={obj.id}
       obj={obj}
       objIndex={index}
       ...unrelated UI Pros 
    />
)}

And in ObjectItem there is an onClick handler that adds some new new to that specific object:

const handleClick = () => {     
    ObjectStore.updateObjectbyIdx(objIndex, newData) 
}

And in the Store

updateObjectbyIdx (index, data) {     
    this.objects[index] = { ...this.objects[index], ...data} 
}

Both react components are wrapped in observer. When the handle click runs, it causes the parent container to rerender and then all of the ObjectItem components to rerender, even though additional data is only added to a single item by index.

Originally I was avoiding using index at all and updating it with map like so:

updateObjectbyId (id, data) {     
    this.objects = this.objects.map(obj => obj.id === id ? { ...obj, ...data} : obj }) 
}

but I assume since this is creating a new array reference, the parent container observing ObjectStore.objects rerenders.

Switching over to index makes me expect that the parent wouldnt rerender as the main array reference still exists, and since I destructured the value out at the latest point, only the single ObjectItem would rerender.

I did end up getting this to work:

updateObjectbyIdx (index, data) {     
    Object.keys(data).forEach(dataKey => {         
        this.objects[index][dataKey] = data.dataKey     
    } 
}

But this seems like a strange workaround needed when all I want to do is update the data in a single object in an array.


Solution

  • By doing this:

    updateObjectbyIdx (index, data) {     
        this.objects[index] = { ...this.objects[index], ...data} 
    }
    

    You are also updating the reference to the object and since parent component uses that object it will rerender.

    Instead of your last solution (which is correct and fine, actually), you can just do Object.assign:

    updateObjectbyIdx (index, data) {     
        Object.assign(this.objects[index], data)
    }
    

    This will keep the reference and only update the properties. Just keep in mind deeply nested properties, objects, arrays. To handle them you better use some sort of mergeDeep function instead, Object.assign does not do the merging, it basically overwrites everything as you did in you custom loop.