React - cloning the same object - reactjs

I am new in React, currently taking a course. While doing the exercise, the teacher cloned the same object on it and I don't understand its utility. I commented the line and all is working perfectly. I thought that the teacher made it by mistake, but he did the same thing in another exercise.
Here is the code:
handleLike = (movie) => {
const movies = [...this.state.movies];
const index = movies.indexOf(movie);
movies[index] = { ...movies[index] }; // It is about this line!
movies[index].liked = !movies[index].liked;
this.setState({ movies });
};
What is the use of this line movies[index] = { ...movies[index] }; ?

This is because you don't want to operate on the original objects from the current state as they are references. The React's state is immutable, so you always want to work on new objects when modifying their values.
movies[index] = { ...movies[index] }; creates a new object with all properties of the original one. In the next line (movies[index].liked = !movies[index].liked;) you are modifying the liked value of the new object. Because you've copied the object before, you don't actually mutate the original one but rather work on the new one.

I'll focus on your question about this particular line:
movies[index] = { ...movies[index] };
1) What does this line does?
This line mutates the movies array, and copy-paste the original content from the same index element.
Then, the next line mutates this element directly.
2) Do we need to do this?
No.
This approach is very common on object-oriented programming languages.
But while using React, an immutable approach is more simple and clean.
Here is an immutable solution for the same function:
handleLike = (movie) => {
const movies = [...this.state.movies];
const index = movies.indexOf(movie);
const updatedMovies = movies.map((movie, movieIndex) => {
if (movieIndex === index) {
return { ...movie, liked: !movie.liked };
} else {
return movie;
}
});
this.setState({ movies: updatedMovies });
};

Related

React setState doesn't work when mutating the state directly why not

Code, i am trying to copy the list as another list and modifying the name property, I was wondering why would this not work, i know that shallow copying this works/ also when i use a fn updater it works , thanks
import { useState } from "react";
const initList = { name: "abhi" };
export default function List() {
const [list, setList] = useState(initList);
function handleClick() {
const nextList = list;
nextList.name = "anand";
// setList((list) => ({ ...list }));
setList(nextList);
}
return (
<>
<button onClick={handleClick}>change name</button>
<ul>{list.name}</ul>
</>
);
}
Edit: I understand the what and how, wanted to know why this happens in Reactjs. I have got my answer, thank you so much guys for helping me out
You've got answers already but to be precise here's the piece of source code from react repo that I believe is responsible for what you're seeing:
// Mark that the fiber performed work, but only if the new state is
// different from the current state.
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
You can find the definition of is function here. The important bit is that it compares two passed objects (old state and a new state) by reference. Since you did not make a shallow copy of the new state these are in fact the same and no update is made.
Perhaps an interesting thing is that using functional version of setState does not automatically save you from the same mistake. It also has to return a different reference from the old state.
In other words, this also won't update state:
const nextList = list;
nextList.name = "anand";
setList((list) => nextList);
const nextList = list will not create a new object, it will create a variable nextList that points to the same object as list.
Consequently, when you mutate the object referenced by nextList, you're mutating the same object referenced by list.
Read more here.
And, as you stated, you already know state in react shouldn't be mutated.
I didn't quite understand your question, but you would like to keep the object's initial value { name: "abhi" } and on const nextList change name to "anand", right? getting like this: initList = { name: "abhi" } and list = { name: "anand" }? If so, just do this:
import { useState } from "react";
const initList = { name: "Test 1" };
export default function List() {
const [list, setList] = useState({ ...initList });
function handleClick() {
const nextList = list;
nextList.name = "Test 2";
// setList((list) => ({ ...list }));
setList(nextList);
}
return (
<>
<button onClick={handleClick}>change name</button>
<ul>{list.name}</ul>
<ul>{initList.name}</ul>
</>
);
}
This happens because you are inserting the object and its pointer to the state of the list, in this case what you need is just the initial values ​​of the object and not the complete one.

Is this the correct way to update a propery in objects array state

I've got the code below and i wanna update name property in the object that has id 1. I'm updating with the code objArray[1].name = "Xxx". It perfectly works but is this correct? Should i use prevState with setObjArray. That looked so much easier what you think?
const [objArray, setObjArray] = useState([
{
id:1,
name:"Eren"
},
{
id:2,
name:"Eren2"
},
{
id:3,
name:"Eren3"
}
])
No this is not advisable. You have the useState second array element (setObjArray) for updating state. Read documentation for React useState . There are two basic ways but there isn't much difference. First method;
const changeName = (id, newName) => {
// map through the array and change the name of the element with the id you are targeting
const changedArr = objArray.map((element) => {
if (element.id === id) {
return {
...element,
name: newName,
};
} else {
return element;
}
});
// set the returned array as you new state
setObjArray(changedArr)
};
Second method;
You have access to the previous state. This way you can make changes on the previous state and return the new array as your new state.
const newChangeName = (id, newName) => {
setObjArray((prev) => {
// map through the array and change the name of the element with the id you are targeting
// set the returned array as you new state
return prev.map((element) => {
if (element.id === id) {
return {
...element,
name: newName,
};
} else {
return element;
}
});
});
};
Hope this helped.
There are many ways to do this. Let me share one way to do this:
Make a shallow copy of the array
let temp_state = [...objArray];
Make a shallow copy of the element you want to mutate
let temp_element = { ...temp_state[0] };
Update the property you're interested in
temp_element.name = "new name";
Put it back into our array. N.B. we are mutating the array here, but that's why we made a copy first
temp_state[0] = temp_element;
Set the state to our new copy
setObjArray(temp_state);

mistakes in filter in React state

I have got an initial state in reducer. I get from api products and I should check by key, if product was not repeated, I put it to the state. If was repeated - do nothing. Mistake is that filter does not work and products repeat.
const defaultState = {
productsInMyList: [],
};
//get prodouct
const { productsInMyList } = action.payload;
//check, if it is in state or not
const sameProductsInMyList =
!!state.productsInMyList.includes(
productsInMyList.key
);
const newProductsInMyList = sameProductsInMyList
? state.productsInMyList
: [...state.productsInMyList, productsInMyList];
return {
...state,
productsInMyList: newProductsInMyList,
};
I suspect, based on a comment of yours, that state.productsInMyList is an array of objects and that productsInMyList is also an object. Array.prototype.includes only works with primitives and shallow object equality (i.e. reference equality).
If you are comparing objects in an array and want to know if an array contains some element matching a criteria then you will want to use Array.prototype.some, i.e. does there exist some element in this array meeting this condition.
const sameProductsInMyList = state.productsInMyList.some(
product => product.key === productsInMyList.key
);
I changed includes into find. Thanks all for the help.
const sameProductInMyList = !!state.productsInMyList.find(
(item) => item.key === productsInMyList.key
);

How to setState with the array that has push method?

My main goal is to set a state with a array of objects, but I can't do it because this array is mutated with elements that are pushed to it. I understand the problem but I don't know how to solve it
so here is code and I would like to set state with the final actors array. Any ideas?
const moviesToMap = this.state.movies;
moviesToMap.map(movie => {
return this.state.title === movie.title
? movie.characters.map(person => {
const actorUrl = person;
const actorId = parseInt(actorUrl.slice(31, -1));
let peopleStateCopy = this.state.people;
peopleStateCopy.map(persona => {
const slugState = persona.url;
const slugId = parseInt(slugState.slice(31, -1));
if (slugId === actorId) {
actors.push(persona);
return persona.name;
}
});
})
You've got a lot going on here - perhaps you could refactor this?
Where is your actors array initialized? Where is your setState call?
When dealing with arrays in state, you may want to create a copy of your arrays - perform the mutations on the copy - and then set the state to the new array when you are finished.
For instance:
const moviesCopy = [...this.state.movies];
this.setState({ movies: moviesCopy });
Hope this helps get you on the right track.

React & Reselect selector claims state is the same after update

I am implementing Reselect in my project and have a little confusion on how to properly use it. After following multiple tutorials and articles about how to use reselect, I have used same patterns and still somethings dont work as expected.
My selector:
const getBaseInfo = (state) => state.Info;
const getResources = (state) => state.Resources;
export const ASelector = createSelector(
[getBaseInfo, getResources],
(items, resources) => {
let result = {};
for(const item in items) {
console.log(item);
result[item] = _.pick(items[item], ['Title', 'Type', 'Beginning', 'minAmount', 'Address'])
}
for(const item in resources) {
console.log(item);
result[item] = {...result[item], firstImage: resources[item].firstImage}
}
return result;
}
);
mapStateToProps component:
function mapStateToProps(state) {
console.log(state);
return {
gridInfo: ASelector(state)
}
}
Now at first my initial state is:
state = { Info: {}, Resources: {} }
My Reducer:
const Info = ArrayToDictionary.Info(action.payload.data.Info);
const Resources = ArrayToDictionary.Resources(action.payload.data.Info);
let resourcesKeys = Object.keys(Resources);
let infoKeys = Object.keys(Info);
let temp = { ...state };
let newInfo;
for (let item of infoKeys) {
newInfo = {
Title: Info[item].Title,
Type: Info[item].Type,
BeginningOfInvesting: Info[item].BeginningOfInvesting,
DateOfEstablishment: Info[item].DateOfEstablishment,
pricePerUnit: Info[item].PricePerUnit,
minUnits: Info[item].MinUnits,
publicAmount: Info[item].PublicAmount,
minInvestmentAmount: Info[item].MinInvestmentAmount,
EinNumber: Info[item].EinNumber,
Address: Info[item].Address,
Status: Info[item].Status,
Lat: Info[item].Lat,
Lng: Info[item].Lng,
CurrencySymbol: Info[item].CurrencySymbol,
Publicity: Info[item].Publicity
}
temp.Info[item] = { ...temp.Info[item], ...newInfo }
}
for (let item of resourcesKeys) {
temp.Resources[item] = { ...temp.Resources[item], ...Resources[item] }
}
return temp;
As a component renders with the initial state, I have an action pulling data from api and saving it accordingly into the state inside reducers.
Now my state is changed, but after debugging a little into reselects code, I found in the comparison function that the old and new states are the same.
Suddenly my "old" state became already populated with the newState data and it of course failing the comparison as they became the same.
Is there anything wrong with my selectors?
I have really tried to use it as the documentation states, but still cant understand how to solve my little issue.
Thank you very much for reading and helping!
It looks like the temp.Info[item] and temp.Resources[item] lines are mutating the existing state. You've made a shallow copy of the top level, but aren't correctly copying the second level. See the Immutable Update Patterns page in the Redux docs for an explanation of why this is an issue and what to do instead.
You might want to try using the immer library to simplify your immutable update logic. Also, our new redux-starter-kit library uses Immer internally.

Resources