Вывод приведенного ниже кода, когда oldRunIn равен undefined
, соответствует ожидаемому при срабатывании эффекта:
Эффект работает
setState выполняется
Тем не менее, в следующий раз, когда useEffect запускается с определенной переменной состояния runInArrow
, которая в функции setState называется oldRunInArrow
, вывод:
Эффект выполняется
setState запущен
setState запущен
setState запущен
Как возможно, что эффект запускается только один раз, но setState запускается 3 раз?
const [runInArrow, setRunInArrow] = useState<mapkit.PolylineOverlay[] | undefined>(undefined);
useEffect(() => {
const trueRunIn = settings.magneticRunIn + magVar;
const boundingRegion = region.toBoundingRegion();
const style = new mapkit.Style({
lineWidth: 5,
lineJoin: 'round',
strokeColor: settings.runInColor,
});
const newRunInArrow = createArrow(boundingRegion, trueRunIn, style);
console.log('Effect is running');
setRunInArrow(oldRunIn => {
// This runs too many times when oldRunIn aka runInArrow is defined
console.log('setState is running');
if (oldRunIn) map.removeOverlays(oldRunIn);
return newRunInArrow;
});
map.addOverlays(newRunInArrow);
}, [magVar, map, mapkit, region, settings.magneticRunIn, settings.runInColor, settings.showRunIn]);
Чтобы понять, почему я использую функцию для установки состояния, посмотрите это post
Edit:
This странно. если я удаляю if (oldRunIn) map.removeOverlays(oldRunIn);
, он работает как положено. Однако, если я изменю его на if (oldRunIn) console.log('oldRunIn is defined');
, он все равно будет выполняться несколько раз. Я полностью сбит с толку.
if (oldRunIn) map.addAnnotations([]);
не запускается несколько раз.
if (oldRunIn) console.log('test');
выполняется несколько раз.
Это выполняется несколько раз (6 раз за использование всегда):
setRunInArrow(oldRunIn => {
console.log('setState is running');
console.log('test');
return newRunInArrow;
});
Это не выполняется несколько раз:
setRunInArrow(oldRunIn => {
console.log('setState is running');
return newRunInArrow;
});
Редактировать 2:
Воспроизводимый пример. Нажмите на кнопку. Вам даже не нужно иметь if (oldState) console.log('Old state');
, который вы можете просто поместить во 2-й console.log, и это изменит поведение.
import { FunctionComponent, useState, useEffect } from 'react';
export const Test: FunctionComponent<> = function Test() {
const [state, setState] = useState<number | undefined>(undefined);
const [triggerEffect, setTriggerEffect] = useState(0);
useEffect(() => {
console.log('effect is running');
setState(oldState => {
console.log('setState is runnning');
if (oldState) console.log('Old state');
return 1;
});
}, [triggerEffect]);
return <>
<button onClick={() => setTriggerEffect(triggerEffect + 1)} type="button">Trigger Effect</button>
</>;
};
Редактировать 3:
I ' мы воспроизвели это, поместив этот код в другой следующий JS проект. Я предполагаю, что это связано со следующими JS.
Edit 4:
Это не Next JS. Это оборачивает это в React.StrictMode. Вот песочница . Почему?
Редактировать 5:
Как указано в ответе, проблема связана с тем, что StrictMode намеренно запускает код дважды. Он не должен запускать useReducer дважды для документов (это проблема с «ответными крючками / исчерпывающим падением» из моего другого вопроса). Вот демонстрация UseReducer, попробуйте с и без StrictMode. Это также работает дважды. Похоже, он тоже должен быть чистым:
CodeSandbox
import { useState, useEffect, useReducer } from 'react';
function reducer(state, data) {
console.log('Reducer is running');
if (state) console.log(state);
return state + 1;
}
export const Test = function Test() {
const [state, dispatch] = useReducer(reducer, 1);
const [triggerEffect, setTriggerEffect] = useState(0);
useEffect(() => {
dispatch({});
}, [triggerEffect]);
return (
<>
<button onClick={() => setTriggerEffect(triggerEffect + 1)} type="button">
Trigger Effect
</button>
</>
);
};
const Home = () => (
<React.StrictMode>
<Test></Test>
</React.StrictMode>
);
export default Home;