При извлечении данных я получаю: Не удается выполнить обновление состояния React для несмонтированного компонента. Приложение все еще работает, но реакция предполагает, что я могу вызвать утечку памяти.
This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function."
Почему я продолжаю получать это предупреждение?
Я попытался исследовать эти решения:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
но это все еще давало мне предупреждение.
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.info(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image = {album.images[0].url}
name = {album.name}
artists = {album.artists}
key = {album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
Редактировать:
В моем файле API я добавил AbortController() и использовал signal, чтобы я мог отменить запрос.
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
Мой spotify.getArtistProfile() выглядит так
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
но поскольку мой сигнал используется для отдельных вызовов API, которые разрешаются в Promise.all я не могу abort() это обещание, поэтому я всегда буду устанавливать состояние.
Невозможно объяснить, почему это происходит, не зная больше о вашем приложении за пределами этого компонента. Нам нужно знать, что заставляет этот компонент монтироваться/размонтироваться. Что происходит в приложении, когда вы получаете ошибку?
@ııı Как мне проверить, размонтировался ли компонент?
Это не настоящая утечка памяти, а, скорее всего, ложное предупреждение — поэтому команда React уберет предупреждение в следующем релизе. См. пиар



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете попробовать установить такое состояние и проверить, смонтирован ли ваш компонент или нет. Таким образом, вы уверены, что если ваш компонент размонтирован, вы не пытаетесь что-то получить.
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if (!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image = {album.images[0].url}
name = {album.name}
artists = {album.artists}
key = {album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
Надеюсь, что это поможет вам.
didMount будет true в несмонтированном состоянии.
Можете ли вы объяснить немного подробнее, почему?
Компонент монтируется, затем запускается эффект и устанавливает didMount на true, затем компонент размонтируется, но didMount никогда не сбрасывается.
Я думаю, вы имели в виду setDidMount(false). Но проблема в том, что замыкание обработчика .then() будет иметь ссылку на более раннее didMount
Это был метод, с помощью которого я решил проблему SSR в своем приложении, и подумал, что он подойдет и для этого случая. Если не обещание должно быть отменено, я думаю.
Ошибка: Rendered more hooks than during the previous render.
Разделение AbortController между fetch() запросами — правильный подход.
Когда Любые из Promise прерывается, Promise.all() отклоняется с AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1ms<script src = "//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src = "//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>Мне помогла очистка состояния в размонтировании компонента.
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
Я не понимаю логики, но это работает.
Объясните кто-нибудь, пожалуйста.
О, кажется, я понял. Функция обратного вызова в useEffect будет выполняться только тогда, когда компонент будет выгружен. Вот почему мы можем получить доступ к реквизитам состояния name и surname до того, как компонент будет выгружен.
Когда вы возвращаете функцию из useEffect, эта функция будет выполняться при размонтировании компонента. Таким образом, воспользовавшись этим, вы устанавливаете свое состояние на пустое. Делая это, всякий раз, когда вы покидаете этот экран или компонент размонтируется, состояние будет пустым, поэтому компоненты вашего экрана не будут пытаться повторно рендериться снова. надеюсь, это поможет
это сработало бы, даже если вы вернете пустую функцию из useEffect. React просто гарантирует, что вы возвращаете функцию из useEffect для выполнения очистки. неважно, какую очистку вы выполняете
У меня была аналогичная проблема с прокруткой вверх, и ответ @CalosVallejo решил ее :) Большое спасибо!!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className = "back-to-top">
<h1
className = "scrollTop"
onClick = {scrollTop}
style = {{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};у вас есть window.addEventListener("scroll", checkScrollTop); рендер
Эта ошибка возникает, когда вы выполняете обновление состояния текущего компонента после перехода к другому компоненту:
Например
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
dispatch(login(res.data.data)); // line#5 logging user in
setSigningIn(false); // line#6 updating some state
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
В приведенном выше случае в строке № 5 я отправляю действие login, которое, в свою очередь, направляет пользователя на панель инструментов, и, следовательно, экран входа в систему теперь отключается.
Теперь, когда React Native достигает строки № 6 и видит, что состояние обновляется, он громко кричит, как мне это сделать, login component больше нет.
axios
.post(API.BASE_URI + API.LOGIN, { email: username, password: password })
.then((res) => {
if (res.status === 200) {
setSigningIn(false); // line#6 updating some state -- moved this line up
dispatch(login(res.data.data)); // line#5 logging user in
} else {
setSigningIn(false);
ToastAndroid.show(
"Email or Password is not correct!",
ToastAndroid.LONG
);
}
})
Просто переместите обновление состояния реакции выше, переместите строку 6 вверх по строке 5.
Теперь состояние обновляется перед уходом пользователя. ПОБЕДА ПОБЕДА
Например, у вас есть компонент, который выполняет какие-то асинхронные действия, затем записывает результат в состояние и отображает содержимое состояния на странице:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect(() => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it needs some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}, []);
return (
<div className = {loading ? "loading" : ""}>
{someData}
<a href = "SOME_LOCAL_LINK">Go away from here!</a>
</div>
);
}
Допустим, пользователь нажимает на какую-то ссылку, когда doVeryLongRequest() все еще выполняется. MyComponent размонтирован, но запрос все еще жив, и когда он получает ответ, он пытается установить состояние в строках (1) и (2) и пытается изменить соответствующие узлы в HTML. Мы получим ошибку от темы.
Мы можем исправить это, проверив, смонтирован ли компонент или нет. Давайте создадим ссылку componentMounted (строка (3) ниже) и установим ее true. Когда компонент размонтирован, мы установим для него значение false (строка (4) ниже). И давайте проверять переменную componentMounted каждый раз, когда мы пытаемся установить состояние (строка (5) ниже).
Код с исправлениями:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
const componentMounted = useRef(true); // (3) component is mounted
// ...
useEffect(() => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it needs some time
// When request is finished:
if (componentMounted.current){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted.current = false; // (4) set it to false when we leave the page
}
}, []);
return (
<div className = {loading ? "loading" : ""}>
{someData}
<a href = "SOME_LOCAL_LINK">Go away from here!</a>
</div>
);
}
Я не уверен в этой информации, но установка переменной componentMounted таким образом, вероятно, вызовет следующее предупреждение: «Назначения переменной 'componentMounted' внутри React Hook useEffect будут потеряны после каждого рендеринга. Чтобы сохранить значение с течением времени, сохраните его в хуке useRef и сохраните изменяемое значение в свойстве .current. ..." В этом случае может потребоваться установка его как состояния, как рекомендуется здесь: stackoverflow.com/questions/56155959/…
Это допустимо, но вы должны использовать хук useRef, чтобы сохранить значение componentMounted (изменяемое значение) или переместить объявление переменной componentMounted внутрь useEffect
Согласитесь, ребята. Фиксированный
Если пользователь уйдет или что-то еще приведет к уничтожению компонента до того, как асинхронный вызов вернется и попытается установить для него setState, это вызовет ошибку. Как правило, это безвредно, если это действительно асинхронный вызов с поздним завершением. Есть несколько способов заглушить ошибку.
Если вы реализуете такой хук, как useAsync, вы можете объявить свои useStates с помощью let вместо const, а в деструкторе, возвращаемом useEffect, установить функцию (функции) setState в неактивную функцию.
export function useAsync<T, F extends IUseAsyncGettor<T>>(gettor: F, ...rest: Parameters<F>): IUseAsync<T> {
let [parameters, setParameters] = useState(rest);
if (parameters !== rest && parameters.some((_, i) => parameters[i] !== rest[i]))
setParameters(rest);
const refresh: () => void = useCallback(() => {
const promise: Promise<T | void> = gettor
.apply(null, parameters)
.then(value => setTuple([value, { isLoading: false, promise, refresh, error: undefined }]))
.catch(error => setTuple([undefined, { isLoading: false, promise, refresh, error }]));
setTuple([undefined, { isLoading: true, promise, refresh, error: undefined }]);
return promise;
}, [gettor, parameters]);
useEffect(() => {
refresh();
// and for when async finishes after user navs away //////////
return () => { setTuple = setParameters = (() => undefined) }
}, [refresh]);
let [tuple, setTuple] = useState<IUseAsync<T>>([undefined, { isLoading: true, refresh, promise: Promise.resolve() }]);
return tuple;
}
Однако это не будет хорошо работать в компоненте. Там вы можете обернуть useState в функцию, которая отслеживает монтирование/размонтирование, и оборачивает возвращаемую функцию setState с проверкой if.
export const MyComponent = () => {
const [numPendingPromises, setNumPendingPromises] = useUnlessUnmounted(useState(0));
// ..etc.
// imported from elsewhere ////
export function useUnlessUnmounted<T>(useStateTuple: [val: T, setVal: Dispatch<SetStateAction<T>>]): [T, Dispatch<SetStateAction<T>>] {
const [val, setVal] = useStateTuple;
const [isMounted, setIsMounted] = useState(true);
useEffect(() => () => setIsMounted(false), []);
return [val, newVal => (isMounted ? setVal(newVal) : () => void 0)];
}
Затем вы можете создать крючок useStateAsync, чтобы немного упростить.
export function useStateAsync<T>(initialState: T | (() => T)): [T, Dispatch<SetStateAction<T>>] {
return useUnlessUnmounted(useState(initialState));
}
Попробуйте добавить зависимости в useEffect:
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [fetchData, props.spotifyAPI])
У меня такое же предупреждение, это решение сработало для меня ->
useEffect(() => {
const unsubscribe = fetchData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
если у вас есть более одной функции выборки, то
const getData = () => {
fetch1();
fetch2();
fetch3();
}
useEffect(() => {
const unsubscribe = getData(); //subscribe
return unsubscribe; //unsubscribe
}, []);
Простой способ
let fetchingFunction= async()=>{
// fetching
}
React.useEffect(() => {
fetchingFunction();
return () => {
fetchingFunction= null
}
}, [])
есть много ответов, но я подумал, что смогу более просто продемонстрировать, как работает abort (по крайней мере, как это решило проблему для меня):
useEffect(() => {
// get abortion variables
let abortController = new AbortController();
let aborted = abortController.signal.aborted; // true || false
async function fetchResults() {
let response = await fetch(`[WEBSITE LINK]`);
let data = await response.json();
aborted = abortController.signal.aborted; // before 'if' statement check again if aborted
if (aborted === false) {
// All your 'set states' inside this kind of 'if' statement
setState(data);
}
}
fetchResults();
return () => {
abortController.abort();
};
}, [])
Другие методы: https://medium.com/wesionary-team/how-to-fix-memory-leak-issue-in-react-js-using-hook-a5ecbf9becf8
Это правильный для проверки прерванный сигнал
варианты = {{ filterType: "флажок" , текстовые метки: { тело: { noMatch: Загрузка? : «К сожалению, нет соответствующих данных для отображения», }, }, }}
Обычно эта проблема возникает, когда вы показываете компонент условно, например:
showModal && <Modal onClose = {toggleModal}/>
Вы можете попробовать сделать несколько маленьких трюков в функции Modal onClose, например
setTimeout(onClose, 0)
Предупреждение связано с тем, что обещание
getArtistProfile()возвращает разрешение после размонтирования компонента. Либо отмените этот запрос, либо, если это невозможно, добавьте проверку в обработчике.then(), чтобыsetArtistData()не вызывался, если компонент был размонтирован.