Как отменить выборку на компонентеWillUnmount
Я думаю, что название говорит само за себя. Желтое предупреждение отображается каждый раз, когда я отключаю компонент, который все еще загружается.
Приставка
Предупреждение. Невозможно вызвать setState
(или forceUpdate
) для неустановленного компонента. Это не работает, но... Чтобы исправить, отмените все подписки и асинхронные задачи в методе componentWillUnmount
.
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
return fetch('LINK HERE')
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
})
.catch((error) =>{
console.error(error);
});
}
Ответы
Ответ 1
Когда вы запускаете Обещание, для его разрешения может потребоваться несколько секунд, и к тому времени пользователь мог перейти в другое место в вашем приложении. Поэтому, когда Promise разрешает setState
, выполняется на размонтированном компоненте, и вы получаете ошибку - как в вашем случае. Это также может привести к утечке памяти.
Вот почему лучше перенести часть вашей асинхронной логики из компонентов.
В противном случае вам нужно как-то отменить свое обещание. В качестве альтернативы - в качестве крайней меры (это антипаттерн) - вы можете оставить переменную, чтобы проверить, смонтирован ли компонент:
componentDidMount(){
this.mounted = true;
this.props.fetchData().then((response) => {
if(this.mounted) {
this.setState({ data: response })
}
})
}
componentWillUnmount(){
this.mounted = false;
}
Я еще раз подчеркну - это антипаттерн, но может быть достаточно в вашем случае (так же, как это было с реализацией Formik
).
Подобное обсуждение на GitHub
EDIT:
Вероятно, именно так я бы решил ту же проблему (не имея ничего, кроме React) с помощью хуков:
ВАРИАНТ А:
import React, { useState, useEffect } from "react";
export default function Page() {
const value = usePromise("https://something.com/api/");
return (
<p>{value ? value : "fetching data..."}</p>
);
}
function usePromise(url) {
const [value, setState] = useState(null);
useEffect(() => {
let isMounted = true; // track whether component is mounted
request.get(url)
.then(result => {
if (isMounted) {
setState(result);
}
});
return () => {
// clean up
isMounted = false;
};
}, []); // only on "didMount"
return value;
}
ВАРИАНТ B: В качестве альтернативы useRef
, который ведет себя как статическое свойство класса, что означает, что он не выполняет перерисовку компонента при изменении его значения:
function usePromise2(url) {
const isMounted = React.useRef(true)
const [value, setState] = useState(null);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
request.get(url)
.then(result => {
if (isMounted.current) {
setState(result);
}
});
}, []);
return value;
}
// or extract it to custom hook:
function useIsMounted() {
const isMounted = React.useRef(true)
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
return isMounted; // returning "isMounted.current" wouldn't work because we would return unmutable primitive
}
Пример: https://codesandbox.io/s/86n1wq2z8
Ответ 2
Приветливые люди в React рекомендуют упаковывать ваши звонки/обещания в отменяемое обещание. Хотя в этой документации нет рекомендаций хранить код отдельно от класса или функции с извлечением, это представляется целесообразным, поскольку другие классы и функции, вероятно, нуждаются в этой функции, дублирование кода является анти-паттерном и, несмотря на длительный код должен быть удален или отменен в componentWillUnmount()
. Согласно React, вы можете вызвать метод cancel()
для обернутого обещания в componentWillUnmount
чтобы избежать установки состояния для размонтированного компонента.
Предоставленный код будет выглядеть примерно так: если мы будем использовать React в качестве руководства:
const makeCancelable = (promise) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => hasCanceled_ ? reject({isCanceled: true}) : resolve(val),
error => hasCanceled_ ? reject({isCanceled: true}) : reject(error)
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
const cancelablePromise = makeCancelable(fetch('LINK HERE'));
constructor(props){
super(props);
this.state = {
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
cancelablePromise.
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
}, () => {
});
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount() {
cancelablePromise.cancel();
}
---- РЕДАКТИРОВАТЬ ----
Я обнаружил, что данный ответ может быть не совсем правильным, следуя за проблемой на GitHub. Вот одна версия, которую я использую, которая работает для моих целей:
export const makeCancelableFunction = (fn) => {
let hasCanceled = false;
return {
promise: (val) => new Promise((resolve, reject) => {
if (hasCanceled) {
fn = null;
} else {
fn(val);
resolve(val);
}
}),
cancel() {
hasCanceled = true;
}
};
};
Идея состояла в том, чтобы помочь сборщику мусора освободить память, сделав функцию или то, что вы используете, пустым.
Ответ 3
Вы можете использовать AbortController для отмены запроса на выборку.
class FetchComponent extends React.Component{
state = { todos: [] };
controller = new AbortController();
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/todos',{
signal: this.controller.signal
})
.then(res => res.json())
.then(todos => this.setState({ todos }))
.catch(e => alert(e.message));
}
componentWillUnmount(){
this.controller.abort();
}
render(){
return null;
}
}
class App extends React.Component{
state = { fetch: true };
componentDidMount(){
this.setState({ fetch: false });
}
render(){
return this.state.fetch && <FetchComponent/>
}
}
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Ответ 4
С тех пор, как сообщение было открыто, была добавлена "abortable-fetch". https://developers.google.com/web/updates/2017/09/abortable-fetch
(из документов :)
Контроллер + сигнальный маневр Познакомьтесь с AbortController и AbortSignal:
const controller = new AbortController();
const signal = controller.signal;
Контроллер имеет только один метод:
controller.abort(); Когда вы делаете это, он уведомляет сигнал:
signal.addEventListener('abort', () => {
// Logs true:
console.log(signal.aborted);
});
Этот API предоставляется стандартом DOM, и это весь API. Он преднамеренно универсален, поэтому его можно использовать в других веб-стандартах и библиотеках JavaScript.
например, вот как вы должны сделать тайм-аут выборки через 5 секунд:
const controller = new AbortController();
const signal = controller.signal;
setTimeout(() => controller.abort(), 5000);
fetch(url, { signal }).then(response => {
return response.text();
}).then(text => {
console.log(text);
});
Ответ 5
Когда мне нужно "отменить все подписки и асинхронно", я обычно отправляю что-то в redux в компонентеWillUnmount, чтобы сообщить всем другим подписчикам и отправить еще один запрос об отмене на сервер, если необходимо
Ответ 6
Суть этого предупреждения заключается в том, что ваш компонент ссылается на него, который удерживается каким-то выдающимся обратным вызовом/обещанием.
Чтобы избежать антипаттерна сохранения вашего состояния isMounted (который сохраняет ваш компонент в живом состоянии), как это было сделано во втором шаблоне, веб-сайт реагирования предлагает использовать необязательное обещание; однако этот код также сохраняет ваш объект в живых.
Вместо этого я сделал это, используя закрытие с вложенной связанной функцией setState.
Здесь мой конструктор (машинопись)...
constructor(props: any, context?: any) {
super(props, context);
let cancellable = {
// it important that this is one level down, so we can drop the
// reference to the entire object by setting it to undefined.
setState: this.setState.bind(this)
};
this.componentDidMount = async () => {
let result = await fetch(…);
// ideally we'd like optional chaining
// cancellable.setState?.({ url: result || '' });
cancellable.setState && cancellable.setState({ url: result || '' });
}
this.componentWillUnmount = () => {
cancellable.setState = undefined; // drop all references.
}
}
Ответ 7
Думаю, я решил обойти это. Проблема заключается не столько в получении, а в setState после отклонения компонента. Таким образом, решение было установить this.state.isMounted
как false
а затем на componentWillMount
изменить его на true, а в componentWillUnmount
снова установить значение false. Тогда только if(this.state.isMounted)
setState внутри извлечения. Вот так:
constructor(props){
super(props);
this.state = {
isMounted: false,
isLoading: true,
dataSource: [{
name: 'loading...',
id: 'loading',
}]
}
}
componentDidMount(){
this.setState({
isMounted: true,
})
return fetch('LINK HERE')
.then((response) => response.json())
.then((responseJson) => {
if(this.state.isMounted){
this.setState({
isLoading: false,
dataSource: responseJson,
}, function(){
});
}
})
.catch((error) =>{
console.error(error);
});
}
componentWillUnmount() {
this.setState({
isMounted: false,
})
}
Ответ 8
Я использую axios
, чтобы сделать запрос к моему API:
componentDidMount() вызывается сразу после монтирования компонента (вставки в дерево)
componentWillUnmount() вызывается непосредственно перед размонтированием и уничтожением компонента
export default class index extends Component{
_isMounted = false;
state = {
produtos : undefined
}
componentDidMount(){
this._isMounted=true;
}
componentWillUnmount(){
this._isMounted = false;
}
onclick(){
async function get(){
await api.post('https://yourapi/Products/Select',{cod_Produto:cod_produto})
.then(function(response){
if (this._isMounted===true){
this.setState({produtos:undefined});
});
}
get();
}
Шрифт: 1 - https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component.
Шрифт: 2 - https://reactjs.org/docs/react-component.html.
Вардар:https://github.com/axios/axios