How to setState with the array that has push method? - reactjs

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.

Related

Only rerender and fetch when api property has changed

Im trying to get the use effect to rerender only when the labels property has changed.
i want to fetch newest changes in the labels property only when there is a change.
My code below:
const usePrevious = (value: any) => {
const ref = useRef()
useEffect(() => {
ref.current = value
}, value)
return ref.current
}
const prevLabels = usePrevious(labels)
useEffect(() => {
if (labels !== prevLabels) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
const fetchUpdatedLabels = async () => {
await axios
.get(`/events/${printEvent.id}`)
.then((response) => {
const newLabels = response.data.labels
// filter response to empty array if there are no splits left
// if is null return empty string
let fetchedLabels =
newLabels !== null
? newLabels
.toString()
.split(',')
.filter((label: string) => {
return label !== ''
}) || ''
: ''
getLabels(fetchedLabels)
print.updateEvent(printEvent.id, 'value', printEvent.value)
})
.catch((err) => {
console.error(err, 'cant fetch labels from api')
})
}
it keeps refetching nonstop, how do i achieve this?
This is most likely in part because you are comparing an array with an array using the equivalence operator !==. It can be surprising to people new to JS but if you do this in a JS console:
[1,2,3] === [1,2,3]
It returns false. The reason is that an array is an object and what you are asking is "is the array on the left literally the same as the one on the right" -- as in the same variable. Whereas these are 2 separate array instances that happen to have the same contents. You might wonder why it works then on strings and numbers etc, but that's because those are primitive values and not objects which are instantiated. It's a weird JS gotcha.
There are several ways to compare an array. Try this:
useEffect(() => {
if (labels.sort().join(',') !== prevLabels.sort().join(',')) {
fetchUpdatedLabels()
}
}, [labels, prevLabels])
I'm not sure about your use case here though as when the labels are different, you fetch them, but the only way they can change is to be fetched? Is there something else in the code that changes the labels? If so, won't they get wiped out by the ones from the server now as soon as they change?
Something else is wrong here I think. Where do you set labels into state?

React state is not updating immediately after setState is being called

I am building the frontend of a web based software and I want to add new note every time I press add button.
But it's simply not happening. New note is being rendered only when I change the state of another object. Right below I ma attaching the code. Please help, I am stuck here.
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
setAllNotes(notesAllTemp)
}
});
}
If anyone can figure this out, please help.
Please don't make mistake by directly updating the array element by its index you should first copy the array into a new array, otherwise, it will directly update the array which will cause reacjs to not to re-render the component.
Check this out
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = [...allnotes]; // !IMPORTANT to copy array
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp);
}
Better if you first modify the array then update at once
const [allnotes, setAllNotes] = useState(notes)
const addNote = () => {
let notesAllTemp = allnotes;
allnotes.forEach((n, index) => {
if((n.id === clickedId)){
notesAllTemp[index].notesDescs.push({id:
notesAllTemp[index].notesDescs.length+1,desc:''})
}
});
setAllNotes(notesAllTemp)
}

React - cloning the same object

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 });
};

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
);

React state is updating without calling setState

My state is changed even i have created new variable and without calling setState.
This is my code
changeName = (event, id) => {
const persons = [...this.state.persons]; // Create a copy of array using spread operator
const person = persons.find(cur => cur.id === id);
person.name = event.target.value;
console.log(persons);
console.log(this.state.persons);
}
render() {
let allPersonsArr = this.state.persons.map(cur => {
return <Person name={cur.name} age={cur.age} job={cur.job} key={cur.id} change={(event) => this.changeName(event, cur.id)}/>;
});
return(
<div>
{allPersonsArr}
</div>
);
}
the state.persons has changed upon checking into the console after using person.name = event.target.value even though i'm pointing to the new array persons
Spread syntax just creates a one level deep copy of the array and not a deep copy and since you have objects inside your arrays, setting person.name changes the original object
changeName = (event, id) => {
const persons = this.state.persons(person => {
if (person.id === id) {
return {...person, name: event.target.value}
}
return persons;
})
this.setState({ persons })
}
Your code actually not change the main object of your state. It just edits a (not deep) copy of the state.
To have an original update you should call setState. Also to be sure that your updates do not effect on a copy, you can use React Immutability Helpers.

Resources