رفع خطای «Called setState() on an Unmounted Component» در React
رفع خطای «Called setState() on an Unmounted Component» در React
مقدمه
یکی از خطاهای رایجی که توسعهدهندگان React با آن مواجه میشوند، خطای «Called setState() on an Unmounted Component» است. این خطا زمانی رخ میدهد که سعی میکنیم وضعیت (state) یک کامپوننت را پس از حذف آن از DOM بهروزرسانی کنیم. این اقدام میتواند منجر به نشت حافظه و کاهش کارایی برنامه شود. در این مقاله، به بررسی علل بروز این خطا و روشهای مختلف رفع آن میپردازیم.
مفهوم کامپوننتهای Mount و Unmount در React
در React، Mount به فرایند اضافه شدن یک کامپوننت به DOM اشاره دارد. هنگامی که یک کامپوننت به DOM اضافه میشود، میگوییم که کامپوننت mount شده است. در مقابل، Unmount به فرایند حذف یک کامپوننت از DOM اشاره دارد. زمانی که یک کامپوننت از DOM حذف میشود، میگوییم که کامپوننت unmount شده است.
علل بروز خطای «Called setState() on an Unmounted Component»
این خطا معمولاً در شرایط زیر رخ میدهد:
-
درخواستهای ناهمزمان (Asynchronous Requests): هنگامی که یک کامپوننت درخواست ناهمزمانی مانند fetch یا axios را ارسال میکند و قبل از دریافت پاسخ، کامپوننت از DOM حذف میشود، در صورت تلاش برای بهروزرسانی state پس از دریافت پاسخ، این خطا رخ میدهد.
-
تایمرها و Intervalها: استفاده از توابع setTimeout یا setInterval برای بهروزرسانی state میتواند منجر به این خطا شود، بهخصوص اگر کامپوننت قبل از اجرای تایمر از DOM حذف شده باشد.
-
لیسنرهای رویداد (Event Listeners): اضافه کردن لیسنرهای رویداد بدون حذف آنها در زمان unmount شدن کامپوننت میتواند باعث بروز این خطا شود، زیرا لیسنرها ممکن است پس از حذف کامپوننت فعال شوند و سعی در بهروزرسانی state داشته باشند.
روشهای جلوگیری و رفع خطا
برای جلوگیری از بروز این خطا و رفع آن، میتوان از روشهای زیر استفاده کرد:
1. استفاده از متغیر mounted برای بررسی وضعیت کامپوننت
میتوان با تعریف یک متغیر mounted در کامپوننت و تنظیم آن در متدهای lifecycle مربوطه، از بهروزرسانی state پس از unmount شدن کامپوننت جلوگیری کرد.
مثال:
class NewsList extends React.Component {
mounted = false;
state = { news: null };
componentDidMount() {
this.mounted = true;
fetch("http://example.com/news.json")
.then(res => res.json())
.then(data => {
if (this.mounted) {
this.setState({ news: data });
}
})
.catch(e => {
alert("Error!");
});
}
componentWillUnmount() {
this.mounted = false;
}
render() {
if (!this.state.news) return "Loading...";
else return this.state.news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
}
در این مثال، قبل از بهروزرسانی state، وضعیت mounted بودن کامپوننت بررسی میشود تا از بروز خطا جلوگیری شود. citeturn0search4
2. استفاده از AbortController برای لغو درخواستهای ناهمزمان
با استفاده از AbortController میتوان درخواستهای fetch را در زمان unmount شدن کامپوننت لغو کرد.
مثال:
class NewsList extends React.Component {
abortController = new AbortController();
state = { news: null };
componentDidMount() {
fetch("http://example.com/news.json", { signal: this.abortController.signal })
.then(res => res.json())
.then(data => {
this.setState({ news: data });
})
.catch(e => {
if (e.name === "AbortError") {
// درخواست لغو شده است
} else {
alert("Error!");
}
});
}
componentWillUnmount() {
this.abortController.abort();
}
render() {
if (!this.state.news) return "Loading...";
else return this.state.news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
}
در این مثال، با استفاده از AbortController، درخواست fetch در زمان unmount شدن کامپوننت لغو میشود تا از بهروزرسانی state پس از unmount شدن جلوگیری شود. citeturn0search4
3. حذف لیسنرهای رویداد در زمان unmount شدن کامپوننت
هنگامی که در متد componentDidMount لیسنرهای رویداد اضافه میکنیم، باید در متد componentWillUnmount آنها را حذف کنیم تا از بروز خطا جلوگیری شود.
مثال:
class OfflineWarning extends React.Component {
state = { online: navigator.onLine };
handleOnline = () => this.setState({ online: true });
handleOffline = () => this.setState({ online: false });
componentDidMount() {
window.addEventListener("online", this.handleOnline);
window.addEventListener("offline", this.handleOffline);
}
componentWillUnmount() {
window.removeEventListener("online", this.handleOnline);
window.removeEventListener("offline", this.handleOffline);
}
render() {
return !this.state.online ? "You're offline!" : null;
}
}
در این مثال، لیسنرهای رویداد آنلاین و آفلاین در زمان unmount شدن کامپوننت حذف میشوند تا از بروز خطا جلوگیری شود. citeturn0search4
۵. استفاده از هوک سفارشی برای مدیریت وضعیت mount کامپوننت
برای سادهسازی مدیریت وضعیت mount بودن کامپوننتها، میتوان یک هوک سفارشی ایجاد کرد که این وظیفه را بر عهده گیرد. این هوک میتواند در هر کامپوننت تابعی استفاده شود تا از بهروزرسانی state پس از unmount شدن جلوگیری کند.
مثال:
import { useRef, useEffect } from 'react';
function useIsMounted() {
const isMounted = useRef(false);
useEffect(() => {
isMounted.current = true;
return () => {
isMounted.current = false;
};
}, []);
return isMounted;
}
function NewsList() {
const isMounted = useIsMounted();
const [news, setNews] = useState(null);
useEffect(() => {
fetch("http://example.com/news.json")
.then(res => res.json())
.then(data => {
if (isMounted.current) {
setNews(data);
}
})
.catch(e => {
alert("Error!");
});
}, [isMounted]);
if (!news) return "Loading...";
else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
در این مثال، هوک useIsMounted
یک مرجع (ref
) به وضعیت mount بودن کامپوننت برمیگرداند که میتوان از آن برای جلوگیری از بهروزرسانی state پس از unmount شدن استفاده کرد.
۶. استفاده از کتابخانههای مدیریت وضعیت (State Management Libraries)
استفاده از کتابخانههای مدیریت وضعیت مانند Redux یا MobX میتواند به جلوگیری از این خطا کمک کند. با مدیریت متمرکز وضعیت برنامه، احتمال بروز بهروزرسانیهای ناخواسته در کامپوننتهای unmount شده کاهش مییابد.
مثال با استفاده از Redux:
import { useSelector, useDispatch } from 'react-redux';
import { fetchNews } from './newsActions';
function NewsList() {
const news = useSelector(state => state.news);
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchNews());
}, [dispatch]);
if (!news) return "Loading...";
else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
در این مثال، وضعیت news در یک استور Redux مدیریت میشود و کامپوننت NewsList نیازی به مدیریت مستقیم وضعیت ندارد، که این امر میتواند به جلوگیری از بروز خطای «Called setState() on an Unmounted Component» کمک کند.
۷. استفاده از کتابخانههای کمکی برای مدیریت درخواستهای ناهمزمان
برخی کتابخانهها مانند axios
قابلیت لغو درخواستهای HTTP را فراهم میکنند که میتوانند به جلوگیری از این خطا کمک کنند.
مثال با استفاده از axios:
import axios from 'axios';
function NewsList() {
const [news, setNews] = useState(null);
useEffect(() => {
const source = axios.CancelToken.source();
axios.get('http://example.com/news.json', { cancelToken: source.token })
.then(response => {
setNews(response.data);
})
.catch(error => {
if (axios.isCancel(error)) {
console.log('Request canceled', error.message);
} else {
alert('Error!');
}
});
return () => {
source.cancel('Component unmounted');
};
}, []);
if (!news) return 'Loading...';
else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
در این مثال، با استفاده از axios.CancelToken
، درخواست HTTP در زمان unmount شدن کامپوننت لغو میشود تا از بهروزرسانی state پس از unmount شدن جلوگیری شود.
۸. استفاده از متدهای lifecycle مناسب در کامپوننتهای کلاسی
در کامپوننتهای کلاسی، استفاده صحیح از متدهای lifecycle میتواند به جلوگیری از این خطا کمک کند.
مثال:
class NewsList extends React.Component {
_isMounted = false;
state = { news: null };
componentDidMount() {
this._isMounted = true;
fetch("http://example.com/news.json")
.then(res => res.json())
.then(data => {
if (this._isMounted) {
this.setState({ news: data });
}
})
.catch(e => {
alert("Error!");
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
if (!this.state.news) return "Loading...";
else return this.state.news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
}
در این مثال، با استفاده از یک فلگ _isMounted
، وضعیت mount بودن کامپوننت ردیابی میشود تا از بهروزرسانی state پس از unmount شدن جلوگیری شود.
۹. استفاده از کنترلکنندههای سیگنال در درخواستهای Fetch
در درخواستهای Fetch میتوان از کنترلکنندههای سیگنال (AbortController
) برای لغو درخواستها در زمان unmount شدن کامپوننت استفاده کرد.
مثال:
function NewsList() {
const [news, setNews] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch('http://example.com/news.json', { signal })
.then(res => res.json())
.then(data => {
setNews(data);
})
.catch(e => {
if (e.name === 'AbortError') {
console.log('Fetch aborted');
} else {
alert('Error!');
}
});
return () => {
controller.abort();
};
}, []);
if (!news) return 'Loading...';
else return news.map((story, key) => <h1 key={key}>{story.Headline}</h1>);
}
در این مثال، با استفاده از AbortController
، درخواست Fetch در زمان unmount شدن کامپوننت لغو میشود تا از بهروزرسانی
- ۰ نظر
- ۲۲ اسفند ۰۳ ، ۲۲:۵۴