React Component gets unmounted and i don't know why - reactjs

I'm a completely new to the whole react world but I'm trying to develop a SPA with a integrated calendar. I'm using react-router for routing, react-big-calendar for the calendar, axios for my API calls and webpack.
Whenever I'm loading my Calender Component it gets mounted and unmounted several times and I think that causes my API call to never actually get any data. I just can't figure out what is causing this.
The Code:
useEffect(() => {
console.log("mounting Calendar")
let source = Axios.CancelToken.source()
if(!initialized) {
console.log("getting Data")
getCalendarEvents(source)
}
return () => {
console.log("unmounting Calendar")
source.cancel();
}
})
const getCalendarEvents = async source => {
setInitialized(true)
setLoading(true)
try {
const response = await getCalendar({cancelToken: source.token})
const evts = response.data.map(item => {
return {
...item,
}
})
calendarStore.setCalendarEvents(evts)
} catch (error) {
if(Axios.isCancel(error)){
console.log("caught cancel")
}else{
console.log(Object.keys(error), error.message)
}
}
setLoading(false)
}
This is the result when i render the component:
Console log
If you need any more code to assess the problem, I will post it.
I appreciate any kind of input to solve my problem.
Thank you

Its because of the useEffect. If you want it to run just once on mount you need to pass an empty array as a dependency like so :
useEffect(() => {
console.log("mounting Calendar")
let source = Axios.CancelToken.source()
if(!initialized) {
console.log("getting Data")
getCalendarEvents(source)
}
return () => {
console.log("unmounting Calendar")
source.cancel();
}
},[])
This means it will only run once. If there is some state or prop you would like to keep a watch on you could pass that in the array. What this means is that useEffect will watch for changes for whatever is passed in its dependency array and rerun if it detects a change. If its empty it will just run on mount.

Related

How to fetch data from api when new data is added to it using UseEffect React

I'm trying to build a chat app, the issue that i'm facing is the following:
When i open the chat the useEffect render the function to fetch all the message inside that particular chat and return it to me, but if i add a new message on that particular chat the hooks is not updated and do not show the new message
Here is the code
const ChatWindow = () => {
const [loadMessage, setLoadMessage] = useState([])
const [message, setMessage] = useState({
message: ""
})
useEffect(() => {
loadMessageInChat()
},[]);
//Api call to load message
const loadMessageInChat = async() => {
try {
const attempt = await axios.get(`https://bbobras.onrender.com/api/messages/${openWindow.chatInfo._id}`,config)
if(attempt.status === 200){
//This will return an [{}]
setLoadMessage(attempt.data.data)
}
} catch (error) {
console.log(error)
}
}
}
export default ChatWindow;
I know that passing an empty array on useEffect will only call loadMessageInChat() once, so i thought to pass the hook loadMessage into it, but doing that will create an infinite loop and fetch the data over and over again because what i get from the api is an [{}] so if i'm not wrong i understand this behaviour as {} === {} --> False and that's why the infinite loop.
Sorry but i'm so junior in react and i'm trying to solve this but i can't get any solution.
Thanks!

react-router-dom not updating useParmams() when Link to same path with different url

It renders fine if I click the link in the <MeetNew> component from a different component, but when a <MeetNew> Link is clicked from the <User> component, the page doesn't load correctly.
on the component
const User = () => {
let { id } = useParams()
let res2;
const [userInfo, setUserInfo] = useState({
user: {},
listItems: []
})
const { listItems } = userInfo
useEffect(() => async () => {
try {
if (id) {
const res2 = await axios.get(`/api/listItems/${id}`)
setUserInfo({ listItems: res2.data })
console.log('render')
}
} catch (err) {
console.error(err)
}
}, [id])
return (
...
)
I feel like I'm not using useParams() correctly or useEffect correctly. When I click the link the URL change is correct, but useParams() doesn't re-render or re-mount my component.
I guess you are referring to pages as components? Is it possible that your id param does not change, so your useEffect is not activated, thus there is no loading of items?
The useEffect looks very suspect to me. It doesn't run any logic as part of the effect other than to return an async cleanup function. This probably isn't what you meant to implement.
Refactor the useEffect callback to declare an async function and only invoke it if there is a truthy id dependency value.
useEffect(() => {
const getItems = async () => {
try {
const res2 = await axios.get(`/api/listItems/${id}`);
setUserInfo({ listItems: res2.data });
} catch (err) {
console.error(err);
}
}
if (id) {
getItems();
}
}, [id]);
What I suspect is happening is that you are using react#18 and rendering the app into a React.StrictMode component. In react#18 the StrictMode component intentionally "double-mounts" react components as a way to ensure Reusable State. The returned cleanup function runs and makes the GET request and the state is updated. The only occurs on the initial mounting/render of the component, subsequent renders occur normally. This part is just a hunch though by looking at just your code without testing a running demo.

React Render UI Before Redirect

I am having problem rendering ui before redirect in react. I has a variable is called from api, i want to check if it is 'x' then will redirect. But ui will render before redirect.
Bellow is my code:
useLayoutEffect(() => {
getProfile().then((res) => {
setIsLoading(true);
if (res) {
redirectByUserType(res.data.type); // redirect to /home
}
});
}, []);
I tried using useLayoutEffect but not working.
Please help me, thank you so much.
If you don't want to render until getProfile() has finished, then have a state variable which tracks whether it is finished. If it hasn't finished, return null to render nothing. If it has, return whatever you want to render. I would normally call this state variable loading, but you seem to already have one with that name, who's purpose i don't know. Maybe you can piggy back on that, maybe you need a separate one:
const [ready, setReady] = useState(false);
useEffect(() => {
getProfile().then(res => {
setIsLoading(true);
if(res) {
redirectByUserType(res.data.type);
} else {
setReady(true)
}
});
}, []);
if (!ready) {
return null;
}
return (
<div>Something</div>
);

Expo React Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application

I'm new to react native and was following a tutorial on medium about how to connect with firebase auth. Everything seems to work fine, but I keep getting this warning below:
Warning: Can't perform a React state update on an unmounted component. 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.
I pretty much did exactly what was said in the tutorial and tried out a few other things to fix it, but nothing seems to work. Here is the code it's pointing the error to:
let currentUserUID = firebase.auth().currentUser.uid;
const [firstName, setFirstName] = useState('');
useEffect(() => {
getUserInfo();
})
async function getUserInfo(){
try {
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
setFirstName(dataObj.firstName)
}
} catch (err){
Alert.alert('There is an error.', err.message)
}
}
It would be great if anyone could help me fix this problem and explain what exactly has gone wrong.
This is the link to the tutorial I was following:
The issue here is that you are potentially enqueueing a state update after a component has unmounted. Since you are accessing your firestore directly, asynchronously, you can use a React ref to track if the component is still mounted before enqueuing the update.
const isMountedRef = React.ref(null);
useEffect(() => {
isMountedRef.current = true; // set true when mounted
return () => isMountedRef.current = false; // clear when unmounted
}, []);
useEffect(() => {
async function getUserInfo(){
try {
let doc = await firebase
.firestore()
.collection('users')
.doc(currentUserUID)
.get();
if (!doc.exists){
Alert.alert('No user data found!')
} else {
let dataObj = doc.data();
if (isMountedRef.current) { // <-- check if still mounted
setFirstName(dataObj.firstName);
}
}
} catch (err){
Alert.alert('There is an error.', err.message)
}
}
getUserInfo();
}, []); // <-- include dependency array, empty to run once when mounting
Your getInfoUser() function is async. You should do something like this in useEffect:
useEffect(async () => { await getUserInfo(); }, [])
As a second argument in useEffect use the dependency array. Using the dependency array is equivalent with componentDidMount.
Your effect will take place only once when the component is first rendered. Additionally there are cases when you need to provide a cleanup function in useEffect something like this:
return () => {
//do something or cancel a subscription
}

How to stop memory leak in useEffect hook react

I am using Effect hook to fetch the datas from server and these data are passed to the react table there i have used the same api call to load the next set of datas from server.
When the application gets loaded i am getting an warning like below
Warning: Can't perform a React state update on an unmounted component. 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.
Effect Hook:
useEffect(() => {
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
setPageLoading(false);
})
.catch((error: string) => {
toast.error(error);
setPageLoading(false);
});
}, []);
React Table Page:
<ReactTable
className="-striped -highlight"
columns={columns}
data={coursesData}
defaultPage={currentPage}
defaultPageSize={courses.perPage}
loading={isLoading}
manual={true}
onFetchData={setFilter}
/>
Set Filter function:
const setFilter = (pagination: any) => {
props.dispatch(updateCoursePageSize(pagination.pageSize));
props.dispatch(updateCourseCurrentPage(pagination.page + 1));
setCurrentPage(pagination.page);
setPerPage(pagination.pageSize);
setLoading(true);
props.dispatch(fetchCourses()).then(() => {
setLoading(false);
});
};
Does anyone know how to clean up the hook in react
Update (June 2022):
React 18 has removed this warning message, and the workarounds to get rid of it may no longer be necessary. Part of the reason they removed it is that it has always been a bit misleading. It says you have a memory leak, but often times you don't.
The code in the question -- and indeed most code that causes this warning -- runs for a finite amount of time past the unmounting of the component, then sets state, then is done running. Since it's done running, javascript can free up variables in its closure, and thus there is usually no leak.
The case where you will have a memory leak is if you are setting up a persistent subscription which continues indefinitely. For example, maybe you set up a websocket and listen to messages, but you never tear down that websocket. These cases do need to be fixed (by supplying a cleanup function to the useEffect) but they are uncommon.
The other reason react 18 has removed the warning is that they are working on the ability for components to preserve their state after being unmounted. Once that feature is in react, setting state after unmount will be a perfectly valid thing to do.
Original answer (September 2019):
With useEffect you can return a function that will be run on cleanup. So in your case, you'll want something like this:
useEffect(() => {
let unmounted = false;
setPageLoading(true);
props
.dispatch(fetchCourses())
.then(() => {
if (!unmounted) {
setPageLoading(false);
}
})
.catch((error: string) => {
if (!unmounted) {
toast.error(error);
setPageLoading(false);
}
});
return () => { unmounted = true };
}, []);
EDIT: if you need to have a call that's kicked off outside of useEffect, then it will still need to check an unmounted variable to tell whether it should skip the call to setState. That unmounted variable will be set by a useEffect, but now you need to go through some hurdles to make the variable accessible outside of the effect.
const Example = (props) => {
const unmounted = useRef(false);
useEffect(() => {
return () => { unmounted.current = true }
}, []);
const setFilter = () => {
// ...
props.dispatch(fetchCourses()).then(() => {
if (!unmounted.current) {
setLoading(false);
}
})
}
// ...
return (
<ReactTable onFetchData={setFilter} /* other props omitted */ />
);
}
you can create a custom hook for that like that :
import * as React from 'react';
export default function useStateWhenMounted<T>(initialValue: T) {
const [state, setState] = React.useState(initialValue);
const isMounted = React.useRef(true);
React.useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const setNewState = React.useCallback((value) => {
if (isMounted.current) {
setState(value);
}
}, []);
return [state, setNewState];
}
Memory leak happens, when a thing that is unnecessary and is supposed to be cleared from memory is kept because some other thing is still holding it. In React Component case, the async call made in component may hold the references of setState or other references and will hold them until the call completes.
The warning you see is from React saying that something is still holding and setting state of a component instance that was removed from tree long back when component unmounted. Now using a flag to not set the state only removes the warning but not the memory leak, even using Abort controller does the same. To escape this situation you can use state management tools that helps dispatching an action which will do processing out side of component without holding any memory references of the component, for example redux. If you are not using such tools then you should find a way to clear the callbacks you pass to the async call (then, catch, finally blocks) when component unmounts. In the below snippet I am doing the same detaching the references to the methods passed to async call to avoid memory leaks.
Event Emitter here is an Observer, you can create one or use some package.
const PromiseObserver = new EventEmitter();
class AsyncAbort {
constructor() {
this.id = `async_${getRandomString(10)}`;
this.asyncFun = null;
this.asyncFunParams = [];
this.thenBlock = null;
this.catchBlock = null;
this.finallyBlock = null;
}
addCall(asyncFun, params) {
this.asyncFun = asyncFun;
this.asyncFunParams = params;
return this;
}
addThen(callback) {
this.thenBlock = callback;
return this;
}
addCatch(callback) {
this.catchBlock = callback;
return this;
}
addFinally(callback) {
this.finallyBlock = callback;
return this;
}
call() {
const callback = ({ type, value }) => {
switch (type) {
case "then":
if (this.thenBlock) this.thenBlock(value);
break;
case "catch":
if (this.catchBlock) this.catchBlock(value);
break;
case "finally":
if (this.finallyBlock) this.finallyBlock(value);
break;
default:
}
};
PromiseObserver.addListener(this.id, callback);
const cancel = () => {
PromiseObserver.removeAllListeners(this.id);
};
this.asyncFun(...this.asyncFunParams)
.then((resp) => {
PromiseObserver.emit(this.id, { type: "then", value: resp });
})
.catch((error) => {
PromiseObserver.emit(this.id, { type: "catch", value: error });
})
.finally(() => {
PromiseObserver.emit(this.id, { type: "finally" });
PromiseObserver.removeAllListeners(this.id);
});
return cancel;
}
}
in the useEffect hook you can do
React.useEffect(() => {
const abort = new AsyncAbort()
.addCall(simulateSlowNetworkRequest, [])
.addThen((resp) => {
setText("done!");
})
.addCatch((error) => {
console.log(error);
})
.call();
return () => {
abort();
};
}, [setText]);
I forked someones code from here to use above logic, you can check it in action in the below link
link
The other answers work of course, I just wanted to share a solution I came up with.
I built this hook that works just like React's useState, but will only setState if the component is mounted. I find it more elegant because you don't have to mess arround with an isMounted variable in your component !
Installation :
npm install use-state-if-mounted
Usage :
const [count, setCount] = useStateIfMounted(0);
You can find more advanced documentation on the npm page of the hook.

Resources