Stop setInterval function written inside a useEffect when navigated to another page - reactjs

I have an API that needs to be fetched every 30 seconds.The UseEffect written below in the code is for a component that gets rendered on home component. It's working well but if I navigate to any another page I need the API to not be fetched.
I'm using react-router and redux.
useEffect(() => {
dispatch(loadCurrencyList())
setInterval(() => {
dispatch(loadCurrencyList())
}, 30000
)
}, [dispatch])

Do a cleanup in the return of useEffect :
useEffect(()=>{
const timer = setInterval(...)
return ()=> clearInterval(timer)
}, [dispatch])

Store timer id in a mutable variable created with useRef() hook,
then use it in the component unmount code. Don't return clean up function from useEffect() dependent on [dispatch] as it will be executed every time dispatch variable changes.
const timer = useRef();
useEffect(() => {
dispatch(loadCurrencyList());
timer.current = setInterval(() => {
dispatch(loadCurrencyList())
}, 30000);
}, [dispatch]);
useEffect( () => () => {
if (timer.current) {
clearInterval(timer.current);
}
}, []);

Related

async call in useEffect is not resolving until the component unmounts

tldr: the await call inside a useEffect hook doesn't resolve itself until after the component starts to unmount, it just hangs until that happens. Not sure why this is happening or how to debug it. This is in a react-native expo project. Swapping the functional component out with a class based one works as expected.
given the following useEffect calls in an expo project
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff();
console.log('fetched');
};
fetch();
}, [depA, depB]);
What I'm seeing in the console when the component is mounted is
'mount'
'fetching'
then when the component is unmounted I see
'unmount'
'fetched'
For some reason, the await call doesn't resolve until the component is unmounted. I've used this pattern in other parts of my code seemingly without issue so I can't figure out why this is happening here. When I swap the functional component out with a class it's working as expected. Any ideas on why this is happening? It looks like the fetchStuff call is being deferred until the component is about to unmount. Swapping fetchStuff out with await new Promise((res) => res(null)); doesn't seem to make any difference
Full component looks something like
function WhatIsHappening({depA, depB}) {
const [stuff, setStuff] = useState([])
useEffect(() => {
console.log('mount');
return () => {
console.log('unmount');
};
}, []);
useEffect(() => {
const fetch = async () => {
console.log('fetching')
const stuff = await fetchStuff(depA, depB);
console.log('fetched');
setStuff(stuff)
};
fetch();
}, [depA, depB]);
return (
<View>
<ListStuff stuff={stuff}></ListStuff>
<View>
)
}
There is something wrong with fetchStuff. This is a working version.
async function fetchStuff() {
return new Promise((resolve) => resolve("fetched"));
}
Working Sandbox

React useEffect prevent on initial mount

i have five a snapshot listener in useEffect and i have another call api to get data from firestore and update state
but I am facing a problem is every initial mount all listener got called , my goal is i want to all listener called only when document changed
i tried with useRef it works but listener do not trigger
As you can see in the example below, onSnapshot is printed during the initial mounted
useEffect(() => {
if (isFirstMount.current) return;
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
}, []);
useEffect(() => {
if (isFirstMount.current) {
isFirstMount.current = false;
return;
}
}, []);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
let snap = await someFirestoreAPICall.get();
setData(snap.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setLoading(false);
};
fetchData();
}, []);
Codesandbox
You can use a condition inside your useEffect block as you are doing, I think. But perhaps useState would be more appropriate here than useRef.
eg:
const [state, setState] = useState(null)
useEffect(()=>{
if (state) {
// do something
}
}, [state])
The useEffect will run on mount and every time you change the value of state, but code inside the condition will only run if you change the state to a truthy value.

React Hooks setInterval with dependency trigger multiple interval

I have a useEffect with setInterval who refresh each seconds data depending on an user id
useEffect(() => {
const interval = setInterval(() => {
refetchUserId(userId);
}, 1000);
return () => clearInterval(interval)
}, [userId]);
But when my userId change (with a setState) the interval is not cleared and i have a lot of getUserId fired
How to fix this
Thanks

Handling multiple useEffect hooks

I am calling useEffect hook multiple times in a component
useEffect(() => {
getData(id);
}, [getData, id]);
const reqBody = useMemo(
() => ({
item: data?.item,
}), [data?.item]
);
useEffect(() => {
if (data?.item) {
getAnotherData(reqBody);
}
}, [getAnotherData]);
As seen in the code snippet, the first useEffect hook calls a function called getData which stores the data in redux store and the reqBody of the function getAnotherData in the second hook depends on the first hook.
Therefore, the second hook runs even if the data in the store is not updated yet resulting in multiple api calls.
How can I avoid this so that the function getAnotherData is called only when the data in the store is updated?
Use data in the dependency array instead of getAnotherData in the second useEffect hook.
useEffect(() => {
getData(id);
}, [getData, id]);
const reqBody = useMemo(
() => ({
item: data?.item,
}), [data?.item]
);
useEffect(() => {
if (data?.item) {
getAnotherData(reqBody);
}
}, [data]);

Detecting Firebase user change with useEffect vs onAuthStateChanged - what's the difference?

I'm using the firebase NPM package with Next.JS/React/Typescript. From what I can tell, there are two ways of watching when a user changes:
useEffect(() => {
// do something
}, [firebase.auth().currentUser])
and something like
const onAuthStateChanged = () => {
return firebase.auth().onAuthStateChanged((user) => {
// do something
});
};
useEffect(() => {
const unsubscribe = onAuthStateChanged();
return () => {
unsubscribe();
};
}, [])
What's the difference here? They both seem to just watch the currentUser; is one preferable over the other?
This will run everytime currentUser changes.
useEffect(() => {
// do something
}, [firebase.auth().currentUser])
This only runs the first time your component mounted. You create a listener to handle changes and remove it when component unmounted.
useEffect(() => {
const unsubscribe = onAuthStateChanged();
return () => {
unsubscribe();
};
}, [])

Resources