Reputation: 3508
For readability im going to strip out a lot of functionality in my examples. However, essentially I have a useEffect
(shown below) that has a dependency that tracks the state.cards
array of card objects. My assumption was that if that state.cards
property changes then the useEffect
should trigger. However, that's not exactly proving to be the case.
Below are two solutions that are being used. I want to use the first one since it's in constant time. The second, while fine, is linear. What confuses me is why the second option triggers the dependency while the first does not. Both are return a clone of correctly modified state.
This does not trigger the useEffect dependency state.cards
.
const newArr = { ...state };
const matchingCard = newArr.cards[action.payload]; <-- payload = number
matchingCard.correct += 1;
matchingCard.lastPass = true;
return newArr;
This does trigger the useEffect dependency state.cards
.
const newArr = { ...state };
const cards = newArr.cards.map((card) => {
if (card.id === action.payload.id) {
card.correct += 1;
card.lastPass = true;
}
return card;
});
return { ...newArr, cards };
useEffect
useEffect(() => {
const passedCards = state.cards.filter((card) => {
return card.lastPass;
});
setLearnedCards(passedCards);
const calculatePercent = () => {
return (learnedCards.length / state.cards.length) * 100;
};
dispatch({ type: 'SET_PERCENT_COMPLETE', payload: calculatePercent() });
}, [learnedCards.length, state.cards]);
State
const initialState = {
cards: [], <-- each card will be an object
percentComplete: 0,
lessonComplete: false,
};
Solution: Working solution using the first example:
const newCardsArray = [...state.cards];
const matchingCard = newCardsArray[action.payload];
matchingCard.correct += 1;
matchingCard.lastPass = true;
return { ...state, cards: newCardsArray };
Why: Spreading the array state.cards
creates a new shallow copy of that array. Then I can make modifications on that cloned array and return it as the new value assigned to state.cards
. The spread array has a new reference and that is detected by useEffect
.
Upvotes: 5
Views: 6735
Reputation: 1375
My best guess is that in the second working example .map returns a new array with a new reference. In the first example you are just mutating the contents of the array but not the reference to that array.
I am not exactly sure how useEffect compares, but if I remember correctly for an object it is just all about the reference to that object. Which sometimes makes it difficult to use useEffect on objects. It might be the same with arrays too.
Why dont you try out:
const newCardsArray = [...state.cards]
// do your mutations here
should copy the array with a new ref like you did with the object.
Upvotes: 8