ПРОБЛЕМА:
Здесь основная проблема связана с расположением строки ниже в коде :
const matches = cards.reduce((matches, { icon, isFlipped }) => {
Итак, чтобы понять проблему, Во-первых, нам нужно понять, что не так с указанным выше положением строки, пожалуйста, прочтите комментарии, чтобы лучше понять проблему
setCards(cards => // <--------- 1.
cards.map((c, i) => // <--------- 2.
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
// 1. current state of cards
// 2. returning updated state of cards
// so, if you are aware of async behavior or setState, then you know
// the below line will always point to snapshot of cards when the useEffect run
// and not to the 1. or 2.
// So here cards is pointing to snapshot when useEffect run
const matches = cards.reduce((matches, { icon, isFlipped }) => {
Теперь давайте посмотрим на оба случая
useEffect(() => {
// Here you will always get the snapshot of cards values, which is currently available
setCards(cards => // <--------- 1.
cards.map((c, i) => // <--------- 2.
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
// so if you understand correctly, above code block doesn't make any sense for the below line
const matches = cards.reduce((matches, { icon, isFlipped }) => {
....
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that works
setPicks([]) // <---- this will trigger useEffect , and send the cards value from 2.
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false })));
}
}, 500)
const timer = setTimeout(() => {
if (picks.length === 2) {
//this order that works
setCards(cards => cards.map((c) => ({ ...c, isFlipped: false }))); // <------------- 3.
setPicks([]) // <---- this will trigger useEffect , and send the cards value from 3.
}
}, 500)
},[picks]); // <----- useEffect is dependent on picks
ДЕМО-ОТЛАДКА :
Вы можете изменить порядок в соответствии с вашим случаем, проверить консоль и посмотреть, какие значения вы получаете для cards
при изменении порядка
Решение:
Надеюсь, это прояснит ваши сомнения. Теперь, если у нас есть четкое представление о проблеме, что тогда решение, здесь мы можем решить его двумя способами, но базовый c является общим в обоих случаях, и это Всегда работайте с обновленным значением карт, в этом случае порядок больше не имеет значения
ПЕРВОЕ РЕШЕНИЕ :
Вы можете поместить блок кода условия соответствия i Помимо setCards(cards => {
и работайте с последними значениями карт
useEffect(() => {
setCards(cards => {
const updated = cards.map((c, i) =>
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
// take whole code block and put it inside the `setCards`
// and directly work with latest update value
const matches = updated.reduce((matches, { icon, isFlipped }) => { // <---- HERE
!matches[icon] && (matches[icon] = 0);
isFlipped && matches[icon]++;
return matches;
}, {});
Object.entries(matches).forEach(([icon, count]) => {
count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
});
return updated;
});
const timer = setTimeout(() => {
if (picks.length === 2) {
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
return () => clearTimeout(timer);
}, [picks]);
РАБОЧАЯ ДЕМО :
ВТОРОЕ РЕШЕНИЕ : (и я бы предложил это)
Вы можете создать useEffect
для значения cards
, чтобы вы всегда получали обновленное значение cards
на основе при этом вы можете установить значения соответствия
useEffect(() => {
setCards(cards =>
cards.map((c, i) =>
picks.includes(c.id) ? { ...c, isFlipped: true } : c
)
);
const timer = setTimeout(() => {
if (picks.length === 2) {
setCards(cards => cards.map(c => ({ ...c, isFlipped: false })));
setPicks([]);
}
}, 500);
return () => clearTimeout(timer);
}, [picks]);
useEffect(() => {
const matches = cards.reduce((matches, { icon, isFlipped }) => {
!matches[icon] && (matches[icon] = 0);
isFlipped && matches[icon]++;
return matches;
}, {});
Object.entries(matches).forEach(([icon, count]) => {
count === 2 && !matched.includes(icon) && setMatched([...matched, icon]);
});
}, [cards]);
РАБОЧАЯ ДЕМО :