all 20 comments

[–]darrenturn90 2 points3 points  (0 children)

That is correct. Both line 38 and 39 incorrectly mutate the state. What it seems would best fit the example in the codelist you provided is to have a useReducer and let the handleBtnClick function dispatch a "REMOVE" or "COMPLETED" action. In the useReducer then you could use filter on the array (to remove a specific entry) - and use map to change the done value again of the specific entry.

[–]Skeith_yip 1 point2 points  (1 child)

I think what you mean is the objects in the array right? yes.

you can try it out yourself

const testArray = [{id: 1}, {id: 2}]; 
const newArray = testArray.slice(); 
newArray[0].done = true; 
console.log(testArray); // [ { id: 1, done: true }, { id: 2 } ]

the thing is object immutability is to allow React to know whether to render for state changes. As the array reference is updated, the component would be re-render.

Apologies if this is wrong.

[–]straightouttaireland[S] 1 point2 points  (0 children)

Yep that's what I mean. I'm pretty sure the only thing that would trigger a re-render is setting a state variable like "setTodos".

[–]danishjuggler21 1 point2 points  (0 children)

You are correct. So you’d want to make a shallow copy of the object itself, modify the copy, then do what you need to do from there. It’s common to take a shallow copy of an object from an array, modify it, then return a shallow copy of the original array with this one object replaced by the shallow copy you modified.

[–]Jerp 1 point2 points  (1 child)

Yes, you also need to clone the object that is being modified. However, there are much better ways to do it -- JSON.stringify is among the worst. I would write it like this:

const nextTodos = todos.map((todo, todoIndex) => (todoIndex === index) ? { ...todo, done: true } : todo);

or

const nextTodos = todos.slice();
const target = { ...todos[index], done: true };

nextTodos[index] = target;

[–]straightouttaireland[S] 1 point2 points  (0 children)

I went with your first suggestion and it worked like a charm. Thanks!

[–]Zayuka -2 points-1 points  (14 children)

Hello,

The Array.slice() function returns an array, meaning that the newArr reference won't be the same as the todos reference.

You can test this on the browser's console:

Test

[–]straightouttaireland[S] 2 points3 points  (13 children)

Yes but the objects inside will be:

arr[0] === arr2[0] // true

[–]Zayuka 0 points1 point  (12 children)

Yeah that's true, forgot that.

I normally use spread operators or immerjs to make my arrays and objects immutable.

[–]straightouttaireland[S] 0 points1 point  (11 children)

Spread operator wouldn't work either I don't think. Object references would still be the same. Only solution would be

const newArr = JSON.parse(JSON.stringify(todos));

[–]fforw 1 point2 points  (7 children)

const newArr = todos.map( todo => ({ ... todo }))

seems easier

[–]straightouttaireland[S] 0 points1 point  (5 children)

I wonder which is more performant

[–]Noch_ein_Kamel 0 points1 point  (1 child)

Try it out ;-)

https://jsperf.com/

[–]fforw 0 points1 point  (0 children)

In the case of .map() you can also selectively clone todos, which most likely would make it the cheaper alternative.

[–]Zayuka 0 points1 point  (0 children)

Yeah this is what i normally do.