6

When I change the property of an object in an array with react hooks, the component does not re-render.

I consulted my lead tech and he explained that React searches "breadth" over "depth," meaning React sees the object, but it can't see changes to the object's properties.

const [array, setArray] = useState([
        {'id': 0, text: '0'},
        {'id': 1, text: '1'},
        {'id': 2, text: '2'}
    ]);

I expect this the component to re-render when the state is changed, but the behavior acts as if the array of objects were not changed at all.

If I create a copy of array as newArray and change newArray[2].text = '3' and setArray(newArray), react thinks the array hasn't changed. Thus, the component does not reload.

As a workaround, I use

const [trigger, setTrigger] = useState(false);
setTrigger(!trigger); 

to force reload. However, this is a hacky way to reload the component and I want a more React way of doing it.

Thank you advance!

2 Answers 2

7

What your lead tech meant by "breath over depth" is right.

React monitors only the "reference" changes. When you change a property of an object, the reference is the same.

Think of it as when you do, you can still change a.value even though it's declared constant.

const a = {value: 'abc'}
a.value = 'xxx'
console.log(a.value) // prints 'xxx'

But you can't do

const a = {value: 'abc'}
a = {something: 'bad'}
👆 not allowed.

The same applies to an array of objects (or a plain old array). It's a reference type, thus changing the property of an array element would not change the reference of newArray.

newArray[2].text = '3' and setArray(newArray)

newArray still points to the same address space.

So when you create an entire new array using a spread syntax or Array.from, you create an entire new reference (a copy) and, set a new value, and lastly set the state.

const [array, setArray] = useState([
        {'id': 0, text: '0'},
        {'id': 1, text: '1'},
        {'id': 2, text: '2'}
    ]);

// ... somewhere else.
const copy = [...array, {'id': 2, ]
// or -> const copy = Array.from(array)
copy[2].text = '3' 
setArray(copy)

The code above creates a new copy of array state. Setting that copy with updated value will trigger the re-render. (but beware, it creates a "shallow copy", not a "deep copy")

Since your data structure is complex, you might as well use ImmerJS, which lets you change the reference as if you are writing current code.

Sign up to request clarification or add additional context in comments.

Comments

3

I think the problem is in how you are copying the array. Arrays in js are reference type so you can't just do this:

const arr = [1,2,4]
const copy = arr

Cause you are just changing the pointer not the reference itself. Try this:

const copy = [...arr]

Now you are creating a new array with the spread of every arr elements. To do that using useState

const Comp = () =>{
    const [arr, setArr] = useState([1,2,3,4])

    return <button onClick={() => setArr([...arr, 5])}>Change</button>
}

Comments

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.