React Hook uses Object.is for comparison, which is a shallow comparison. This also means that directly modifying the value of an object does not trigger the component’s rerendering. However, if we need to update a deeply nested data, in order to trigger React re-rendering, we need to update the entire object. This may cause performance problems. Is there a good way to solve this problem?

Look at the problem from a 🌰

import React, { useEffect, useMemo, useState } from 'react'; import update from 'immutability-helper'; import { Button } from 'antd'; import Child from './Child'; import { cloneDeep } from 'lodash'; const Test = () => { const [data, setData] = useState({ info: { name: 'tom', age: 12, }, score: { exam1: [99, 98, 89], exam2: [78, 85, 33], }, }); Function handleClick() {// TODO: const examStr = useMemo(() => {const exam1 = data.score.exam1; Return (< div > < p > the language: {exam1 [0]} < / p > < p > mathematics: {exam1 [1]} < / p > < p > English: {exam1 [2]} < / p > < / div >). }, [data.score.exam1]); Return (<div> <Button onClick={handleClick}> </Button> <div>{examStr}</div> <Child Child ={data.info}></Child> </div> ); }; export default Test;Copy the code

Looking at the code above, we need to update the third entry of the exam1 array when we click the button.

Implementation Mode 1 (Failure)

data.score.exam1.push(100);
setData(data);
Copy the code

Implementation Mode 2 (Failure)

data.score.exam1[2] = Math.random() * 100; setData({ ... data, });Copy the code

Implementation 3 (success, but not recommended)

import { cloneDeep } from 'lodash';
​
data.score.exam1[2] = Math.random() * 100;
setData(cloneDeep(data));
Copy the code

We can update exam1 successfully by making a deep copy of the data and returning a new object. However, we can also cause a new problem. We only need to update Exam1, but the info also becomes a new object, causing the Child component to be re-rendered.

Implementation mode 4 (Success)

data.score.exam1[2] = Math.random() * 100; setData({ ... data, score: { ... data.score, exam1: [...data.score.exam1], }, });Copy the code

conclusion

To update an array of a certain value, we tried the above four ways, there are two of them are successful, the last way is better, but only use the last in the update data at the same time, as far as possible to reduce the damage to the other data reference, but we have only three layers of sample data, we use three times in the code… Extending the operator, if the level is deeper, can be particularly troublesome to update.

Use the immutability – helper

How can I change data gracefully and efficiently? Here I use immutability-Helper

setData((data) => {
  return update(data, {
    score: {
      exam1: {
        2: {
          $set: Math.random() * 10,
        },
      },
    },
  });
});
Copy the code

Using immutability-Helper can adjust data as needed, and it will only adjust the data that needs to be modified, and reuse the unmodified data, which is consistent with the effect of implementation 4.

The API is introduced

$push adds data to the end of data

Const [data, setData] = useState<any[]>([1,2]); SetData ((data) => {// data is [1,2,3,4] return update(data, {// $push: [3, 4],}); });Copy the code

$unshift adds data to the beginning of the array

Const [data, setData] = useState<any[]>([3,4]); SetData ((data) => {// data is [1,2,3,4] return update(data, {// $unshift must be an array $unshift: [1,2],}); });Copy the code

$splice Modifies array data, including adding and deleting data

Const [data, setData] = useState<any[]>([3,4]); SetData ((data) => {// data = [3,6,5] return update(data, {// $splice: [[1,1,6,5]],}); });Copy the code

$set assigns a value to an element of the object

const [data, setData] = useState<any[]>([ { user: [ { name: 'superfeng', }, ], }, ]); SetData ((data) = > {/ / modify the name of the value of the return the update (data, {0: {user: {0: {name: {$set: "feng",},},},},}); });Copy the code

$unset Removes elements from the object

Const data, setData] = useState < any [] > [[{user: [{name: 'superfeng' sex: 'male'},],},]); / / will sex is removed from the object setData ((data) = > {return update (data, {0: {user: {0: {$unset: [' sex '],},},},}); });Copy the code

$merge Merges objects

Const data, setData] = useState < any [] > [[{user: [{name: 'superfeng' sex: 'male'},],},]); setData((data) => { return update(data, { 0: { user: { 0: { $merge: { age: 16, }, }, }, }, }); });Copy the code

$apply passes the current value to the function and updates it with the new return value

Const data, setData] = useState < any [] > [[{user: [{name: 'superfeng' sex: 'male'},],},]); setData((data) => { return update(data, { 0: { user: { 0: { $apply: (user: any) => { return Object.assign({}, user, { age: 15 }); },},},},}); });Copy the code

Simplified immutabilty – helper

Although immutabilty-Helper can update data on demand, it is also troublesome to write data with many levels. Is there a better way to update data? I’ve wrapped a function that looks like this

const toImmutability = (path: string, value: any) => { const arrReg = /[(\d+)]/; const keys = path.split('.').filter((item) => item); const result = {} as any; let current = result; const len = keys.length; keys.forEach((key: string, index: number) => { const matches = key.match(arrReg); if (matches && matches.length) { const idx = parseInt(matches[1]); current[idx] = index === len - 1 ? value : {}; current = current[idx]; } else { current[key] = index === len - 1 ? value : {}; current = current[key]; }}); return result; };Copy the code

Using this function, we can update the data in the following way

SetData ((data) => {return update(data, toImmutability('[0].user.[0]. Name ', {$set: '$set ',})); });Copy the code

The first argument to the toImmutability function is a string concatenated from the paths to be updated, represented by the array [index], which is then parsed inside the function to build the object to be updated

\