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.
statelooks like?{ ...state }- This won't clone the objects inside thestate. Meaning you are mutating thecardsarray directly. This doesn't changes thestate.cards, henceuseEffectis not triggered. Mapping over thestate.cardsis the way to do it.{ ...state }- This is making a new object, copying the properties ofstatein the new object but objects are reference types, so you are basically creating a property namedcardsin the new object but its value is the same array reference which was pointed to bycardsproperty instateobject.passedCards.lengthto pass the correct value in the same useEffect cycle.