how to write a clean up function in react? - reactjs

I keep trying to understand how to fix memory leaks in useEffect() but still am having trouble understanding.
The error code says in order to fix the leak I must cancel all subscriptions and asynchronous calls with a clean up function.
This is how my code looks like at the moment.
const [dish,setDish] = useState({title:""})
useEffect(()=>{
fetchDish()
},[dish.title])
const fetchDish = async() => {
const apiCall = await fetch('https://exampleurl.json')
const dishes = await apiCall.json()
setDish(dishes[props.dishID] )
}
I am assuming i'm getting a memory leak because I have to destroy my dish object once this unmounts?

You need to use componentWillUnmount technique here, as this is a functional component returning from useEffect will cause the same behavior.
For Example:
useEffect(() => {
fetchDish()
return () => {
//unmounting code here..
//in your case
setDish({})
}
}, [dish.title])

First, I think you should read this article by Dan Abramov to get a better understanding of useEffect
If you only use the function in your useEffect, then move it inside the useEffect so it doesn't affect the data flow. If you want to re-use the fetchDish, then use useCallback so it doesn't get called if you don't need it.
If your case is about cleaning the code, then Rishav's answer is correct
useEffect( () => {
subscribe();
return () => unsubscribe();
}
However I think what react is trying to tell you is because of your asyncrhonous call that keep getting called
Try to modify your code to this
const [dish,setDish] = useState({title:""})
useEffect(()=>{
async function fetchDish(){
const apiCall = await fetch('https://exampleurl.json')
const dishes = await apiCall.json()
setDish(dishes[props.dishID] )
}
fetchDish()
},[])

Related

react useEffect with async-await not working as expected

I have a useEffect hooks in my component, which makes a API call and I want it to run only on first render. But I'm unable to make the API call. what am I missing here?
useEffect(() => {
//should run on first render
(async () => {
const getAllSeasons = await getSeasonList();
setSeasons(getAllSeasons);
})();
}, []);
const getSeasonList = async () => {
if (state && state?.seasonList) {
return state?.seasonList;
} else {
const seasonData = await useSeasonService();
if (seasonData?.status === "loaded") {
return seasonData?.payload?.seasons || [];
} else if (seasonData.status == "error") {
return [];
}
}
};
Best way to fetch APIs using fetch method
fetch('https://apisaddress.me/api/')
.then(({ results }) => consoe.log(results) ));
Another aproach is using axios
const Func= async ()=>{
const response= await axios.get('https://pokeapi.co/api/v2/pokemon?
limit=500&offset=200')
this.setState({Data:response.data.results})
}
// npm install axios
Make the new function and clean all code from useEffect and put inside that function. and then call the function inside the useEffect. Like:
const sampleCall = async () => {
const getAllSeasons = await getSeasonList();
setSeasons(getAllSeasons);
}
useEffect(() => {
sampleCall()
}, [])
Follow these steps, if it is still not working then try to add seasons inside the useEffect array, [seasons].
Thank You.
useEffect works fine. The proof is here https://codesandbox.io/s/set-seasons-9e5cvn?file=/src/App.js.
So, the problem is in getSeasonList function.
await useSeasonService() won't work. React Hooks names start with use word and can't be called inside functions or conditionally. useSeasonService is considered by React engine as a custom hook. Chek this for more details:
https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook
Your code example doesn't show what state is and how it's initialized.
state && state?.seasonList check is redundant. state?.seasonList is enough.
It's a bad practice to put such complex logic as in getSeasonList into React components. You'd better use some state container (Recoil might be a good choice if you have some small non-related states in the app).
P.S. You wrote a poor description of the problem and then gave minuses for answers. That's not fair. Nobody would help you if you'll continue to do such thing. You gave 4 minuses and received 4 minuses in response. It's better to clarify what's wrong before giving a plus or minus to any answer.

apply useEffect on an async function

I have a functional component in React:
export default function (id) {
const [isReady] = useConfig(); //Custom hook that while make sures everything is ok
useEffect( () => {
if(isReady)
renderBackend(id);
});
async function renderBackend(id) {
const resp = await getBackendData(id);
...
...
}
}
Now, I am passing some props to the Functional Component like this:
export default function (id, props) {
const [isReady] = useConfig(); //Custom hook that while make sures everything is ok
useEffect( () => {
if(isReady)
renderBackend(id);
});
async function renderBackend(id) {
const resp = await getBackendData(id, props); // Passing props to backend
...
...
}
}
Here the props are dynamic based on user input and changes time on time. But my code here is only rendering for the first prop, not on subsequent props. I want to call the backend every time props get updated or being passed. I think we might use useEffect for this, but not totally sure. And I cannot replicate this is codeSandbox as the real code is very complex and have trimmed down to mere basics.
Change
useEffect( () => {
if(isReady)
renderBackend(id);
});
to
useEffect( () => {
if(isReady)
renderBackend(id);
}, [id]);
so useEffect function runs every time id changes
As you do not put useEffect dependency, it will be executed for every re-render (state changed, ...)
So to execute the code within the useEffect every props change, put it to the useEffect dependencies list.
useEffect( () => {
if(isReady)
renderBackend(id);
}, [id, props]);
best practice is destructure your props and put only the affected value in the dependencies.
When using React.useEffect() hook consider that you have to pass the dependencies to gain all you need from a useEffect. The code snippet is going to help you to understand this better.
useEffect(() => {
console.log('something happened here');
}, [one, two, three]);
every time that one of the items passed to [one, two, three] you can see something happened here in your browser developer tools console.
I also should mention that it is not good idea at all to pass complex nested objects or arrays as a dependency to the hook mentioned.

Ho to wait with fetch until redux state is available?

I want to fetch some data from a database, and depending on the user the returned data should differ. The way i tried it, was by passing the userid as a query. The id is stored as a redux state. The problem is, that it takes some time before the redux state is available. Ive tried fixing this with if statements, and rerunning the useEffect everytime the auth state is updated. This doesn't work.
I want to fetch, when the redux state auth.user.id is available. Which it is like .1 sec after the initial load.
Here is my code:
const auth = useSelector((state) => state.auth);
useEffect(async () => {
if (auth.token.length > 0) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}
}, [auth, date]);
I believe useEffect is already asynchronous, so you don't need to use the async keyword in the anonymous callback. You can create the async function for that logic elsewhere and call it within the useEffect.
Similarly, you could put in self calling async function within your useEffect as such:
useEffect(() => {
(async () => {
if (auth.token.length) {
try {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
}catch (err) {console.log(err);}
}
})();
}, [auth, date]);
I think this link may be helpful:
React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing
So with the basic understanding, I assume that you need to call the API whenever userId is available. try the below useEffect
useEffect(async () => {
// check user id is available here
if (auth.user && auth.user.id) {
const res = await getData(`calendar/?userId=${auth.user.id}`);
setLessons(res.lessons);
setEvents(res.events);
// some other statements
}
}, [auth, date]);

How to use useEffect when there are dependencies I don't care about?

useCallback(() => {
async function awaitAllPendingTxs() {
const txReceiptPromises = txsPending.map((tx) => {
return provider.waitForTransaction(tx.hash);
});
const txReciepts = await Promise.all(txReceiptPromises);
txReciepts.forEach((tx) =>
dispatch(actions.rmTxsPending(tx.transactionHash))
);
}
awaitAllPendingTxs();
}, [provider]);
I only want this to run when the app first loads.
And more specifically when provider is available. provider is initialized as null. At some point when the app first loads, provider is initialized. That's when I want this to run.
But the React hooks plugin is telling me to add dispatch and txsPending.
I don't want the code to run when txsPending and dispatch changes. And yet, I need these variables and functions in the useCallback.
If I add txsPending and dispatch to the dep array, won't the codeblock be called three times?
Once when dispatch is created, and everytime txsPending is changed (txsPending is localstate)? Which I don't want.
How is useCallback dep array satisfied here? While also making sure it only runs when I want it to?
It's a best practice not to lie about your dependency array when using useEffect because it can cause problems later on.
try this instead:
const [avoidExtraCall, setAvoidExtraCall] = useState(false);
useCallback(() => {
if(provider && !avoidExtraCall){
async function awaitAllPendingTxs() {
const txReceiptPromises = txsPending.map((tx) => {
return provider.waitForTransaction(tx.hash);
});
const txReciepts = await Promise.all(txReceiptPromises);
txReciepts.forEach((tx) =>
dispatch(actions.rmTxsPending(tx.transactionHash))
);
}
awaitAllPendingTxs();
setAvoidExtraCall(true);
}
}, [provider, txsPending]);
The best you can do is to store (or clone) txsPending, in a useRef, and update the value in the proper time, so that useEffect could always have the latest value.
For dispatch I guess it's value is always static, so it can be safely captured by the closure; however the eslint is not parsing it correctly. I think you could ignore it, if you accept keeping the eslint warnings, otherwise you might also need to clone it in a useRef.
P.S. I think you mean useEffect instead of useCallback? Since you dont assign the result of useCallback to any variable for later use?
The codes:
useEffect(() => {
async function awaitAllPendingTxs() {
// This line, use a ref
const txReceiptPromises = txsPendingRef.current.map((tx) => {
...
// ignore warning, or use something like dispatchRef.current
txReciepts.forEach((tx) =>
dispatch(actions.rmTxsPending(tx.transactionHash))
);
}
awaitAllPendingTxs();
}, [provider]);

React Hooks , function fetchData is not a react component?

I'm playing around with react hooks and I ran into a weird issue trying to fetch some data, when i'm creating a file to fetch data using hooks and axios
this works
import axios from 'axios';
const useResources = (resource) => {
const [resources, setResources ] = useState([]);
useEffect(
() => {
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
setResources(response.data);
})(resource);
},
[resource]
);
return resources;
};
export default useResources;
but this doesn't
import { useState, useEffect } from 'react';
import Axios from 'axios';
const fetchData = () => {
const [data, setData] = useState('');
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
});
return data;
};
export default fetchData;
'React Hook useEffect contains a call to 'setData'. Without a list of dependencies, this can lead to an infinite chain of updates. To fix this, pass [] as a second argument to the useEffect Hook.'
Aren't they the same?
On first glance, they are similar, but they still have differences.
Let's check them:
useEffect(
() => {
// we create a function, that works like a "black box" for useEffect
(async resource => {
const response = await axios.get(`randomapi.com/${resource}`);
// we don't use `setResources` in dependencies array, as it's used in wrapped function
setResources(response.data);
// We call this function with the definite argument
})(resource);
// this callback doesn't return anything, it returns `undefined`
},
// our function depends on this argument, if resource is changed, `useEffect` will be invoked
[resource]
);
useEffect hook should receive a function, which can return another function to dispose of all dynamic data, listeners (e.g. remove event listeners, clear timeout callbacks, etc.)
Next example:
// this function returns a promise, but actually useEffect needs a function,
// which will be called to dispose of resources.
// To fix it, it's better to wrap this function
// (how it was implemented in the first example)
useEffect( async () => {
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
// we don't set up an array with dependencies.
// It means this `useEffect` will be called each time after the component gets rerendered.
// To fix it, it's better to define dependencies
});
So, we have 2 major errors:
1) Our useEffect callback returns a Promise instead of function, which implements dispose pattern
2) We missed dependencies array. By this reason component will call useEffect callback after each update.
Let's fix them:
useEffect(() => {// we wrap async function:
(async () => {
// we define resource directly in callback, so we don't have dependencies:
const response = await Axios('randomapi.com/word?key=*****&number={number_of_words}');
setData(response.data);
})()
}, []);
Finally, we have fixed our second example :)
I even made an example with custom fetchData hook and final version of useEffect: https://codesandbox.io/s/autumn-thunder-1i3ti
More about dependencies and refactoring hints for useEffect you can check here: https://github.com/facebook/react/issues/14920

Resources