Can I not call a "useEffect" inside a function in React? - reactjs

Here's my usecase for which I would really want useEffect be called within a function but I'm unable to do so.
I have a dropdown which gets it's data from an API call that I call at the start of the page load. Based on the selection from this dropdown, I do a setState for the ID of the selected value taken from the dropdown. I then have another dropdown which is part of a library called React QueryBuilder for which I need to pass an onChange function. Within this onChange function, I call useEffect and then pass the ID that I got from the previously mentioned setState - When I do this, I get an error message telling me I cannot call useEffect within a function but I need it to be called here because I need to make an API call and pass the ID to the API.
If i have the function name in all small cases, I get the below error:
React Hook "useEffect" is called in function "functionname" which is neither a React function component or a custom React Hook
If i rename the function as "FunctionName", I get the below error:
*
Invalid hook call. Hooks can only be called inside the body of a
function component
If i don't use useEffect, that API call goes into a continuous loop!
I'm unable to think of any other way - Can someone shed some light or help me in the right direction on what to do here ?
Update:
For API Calls, I am currently using "useEffect" and "axios" - is that wrong? I add the "useEffect" if there's a continuos loop problem. See below example:
const ABCFunction = (event) => {
axios.get("APIURL/Getsomething").then((response) => { setState(response.data);
});
In one part of the code, the above did not go into a continuous loop, but in another part where I had called my API the same way, it went into a continuos loop, so I added useEffect which gave me my issue.
Here's the API call which I did with the useEffect hook - this Function is located inside the page.
import abc
import cdf
..
..
const pageView =() => {
.
.
.
.
const getFunction = (fieldName) => {
const field = filteredIfData.find(fld => fld.name === fieldName);
useEffect(() => {
axios.get(`$apiURL`).then((response) => {
setState(response.data);
},[]);
.
.
.
};
export default pageView

From your description and error message, I got two key things what you did wrong:
Calling useEffect hook from onChange handler:
onChange={FunctionThatCallsUseEFfectHook}
Without useEffect hook, getting infinite loop through immediate call
onChange={FunctionThatCallsImmediately()}
Solutions:
You can't use the useEffect hook from event handlers. Rather, use the useEffect hook based the value change:
useEffect(() => { // not inside onChange handler
//...
}, [ValueThatChangesOnSelection])
Without useEffect hook, call the function only on change but not immediately:
onChange={() => onChangeHandler()}
Or,
onChange={onChangeHandler}
But not this:
onChange={onChangeHandler()}

Related

How to implement promise when updating a state in functional React (when using useState hooks)

I know similar questions are bouncing around the web for quite some time but I still struggle to find a decision for my case.
Now I use functional React with hooks. What I need in this case is to set a state and AFTER the state was set THEN to start the next block of code, maybe like React with classes works:
this.setState({
someStateFlag: true
}, () => { // then:
this.someMethod(); // start this method AFTER someStateFlag was updated
});
Here I have created a playground sandbox that demonstrates the issue:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-6zss6q?file=/demo.tsx
Please push the button to get the confirmation dialog opened. Then confirm with "YES!" and notice the lag. This lag occurs because the loading data method starts before the close dialog flag in state was updated.
const fireTask = () => {
setOpen(false); // async
setResult(fetchHugeData()); // starts immediately
};
What I need to achieve is maybe something like using a promise:
const fireTask = () => {
setOpen(false).then(() => {
setResult(fetchHugeData());
});
};
Because the order in my case is important. I need to have dialog closed first (to avoid the lag) and then get the method fired.
And by the way, what would be your approach to implement a loading effect with MUI Backdrop and CircularProgress in this app?
The this.setState callback alternative for React hooks is basically the useEffect hook.
It is a "built-in" React hook which accepts a callback as it's first parameter and then runs it every time the value of any of it's dependencies changes.
The second argument for the hook is the array of dependencies.
Example:
import { useEffect } from 'react';
const fireTask = () => {
setOpen(false);
};
useEffect(() => {
if (open) {
return;
}
setResult(fetchHugeData());
}, [open]);
In other words, setResult would run every time the value of open changes,
and only after it has finished changing and a render has occurred.
We use a simple if statement to allow our code to run only when open is false.
Check the documentation for more info.
Here is how I managed to resolve the problem with additional dependency in state:
https://codesandbox.io/s/alertdialog-demo-material-ui-forked-gevciu?file=/demo.tsx
Thanks to all that helped.

How combine 3 lifecycles in one in useEffect in the following case?

I have a useEffect where currently get from the redux the data and also clean up it during the unmounting:
useEffect(
dispatch(getData...)
() => {
dispatch(cleanData...)
},
[url]
);
When I set the url as dependency for using as componentDidupdate and updating the component if url is changed it throws some warnings in the console about not being able to work with unmounted component while functionality seems to work. What is the ideal way to have these 3 lifecycle methods in the same place for the useEffect?
useEffect expects two arguments. Callback Function that will get called at the initial render and when the dependencies gets changed. Second argument is dependency array.
So you can call dispatch function on initial render and on change of url . You have to return a function that is a cleanup function.
useEffect(() => {
dispatch(getData)
return () => {
dispatch(cleanData)
}
},[url]);

React, wait for a hook to finish before starting another hook

I have a component like follows
export const Component = () => {
const { data: item} = useItem();
const { list } = useItemList(item?.id.toString());
return(
item ? (<p>some stuff</p>) : (<p>loading</p>)
)
}
Problem is the app is not waiting for item to be available and it runs useItemList while its undefined, but i've to wait to fetch item
How can I solve so?
you cannot run the hook conditionally because React relies on the order in which Hooks are called
React cannot track the state correctly if a hook is skipped
An easy solution is to modify your useItemList code, if the argument is undefined, then don't call whatever is inside it.
update: my solution is incorrect because the hook value is initial value, so it wont work.
this is the correct solution:
You should return a memorized callback from useItemList instead and run this callback in useEffect with item and that callback(optional) as dependency

How to reduce API calls in a react functional component?

Overview
The below functional component renders the list of users in a dropdown. In the edit mode, we can change the current user to another user from the list of users.
Expected
The given code is fetching details thrice. I need to reduce it to one.
function UsersListView(props){
const { edit } = props // this is true for this case.
const refreshUsers = useRef(true);
const [error,users,refersh]=useFetchData('/profiles')
useEffect(() => {
if(edit && refreshUsers.current){
const fetchData = async () => {
await refresh();
}
fetchData();
refreshUsers.current = false;
}
},[edit, refresh])
return (
...... JSX CODE TO RENDER USERS
)
}
In the above code, we are making 3 API calls.
In the initial render
When we refresh the data => We fetch the data again. => API called
As refresh is in the dependency of useEffect() hook a re-render occurs then calling useFetchData() => API called.
My attempt
Initially refershUsers.current=false is under await refresh() which got me 8 API calls.
The above-mentioned code still gives me 3 API calls.
I tried moving const [error,users,refersh]=useFetchData('/profiles') inside useEffect() which throws an error. Hooks can't be used inside callbacks.
Queries
Is there a way to reduce the API calls to 1.
Why were the API calls reduced from 8 to 3 when I moved the line refershUsers.current=false outside
Try using a separate useEffect for each dependency. React allows you to use multiple useEffect within the same component.
one of them could be with an empty dependency array for the initial fetch. and then update state or make additional fetch calls as needed in another useEffect

Using redux data in the useEffect hooks

I am now trying to call an API using data from the redux store.
Let say I got 2 API calls, Api A and Api B Inside the parent component I already called the API A and save the data inside the redux already.
Now I am in another component. I need to call Api B. But API B has a params, which I will get from API A. So Inside the Second component, I am using useEffect hook to call the data.
To get the params from the redux, I am using useSelector Hook.
Inside the second component, UseEffect Hook is something like this:
useEffect(() => {
let splitText = cartList?.OrderDTO?.DeliveryCountry;
let deliveryAddressId = splitText?.split(',');
if (cartList.OrderDTO?.DeliveryCountry !== '') {
dispatch(getShippingMethodById(token.access_token, deliveryAddressId));
} else {
dispatch(
getShippingMethodById(
token.access_token,
cartList.OrderDTO?.CustomerAddressId,
),
}
}, []);
So in the useEffect hook, I got the deliveryAddressId from redux. To draw in data from the redux into component, I am using useSelector hook
let cartList = useSelector((state) => state.productReducer.cartList);
The problem is that I always get undefined for cartlist when ever I tried to access it inside the useEffect hook
So the dispatch called are always getting undefined. So What can I do to make this hooks works?
You should add cartList to your dependency array, so the useEffect hook watches for updates to that piece of state. As it is written now, the useEffect only runs on the first render, where cartList is probably undefined.
React - useEffect Docs
useEffect(() => {
let splitText = cartList?.OrderDTO?.DeliveryCountry;
let deliveryAddressId = splitText?.split(',');
if (cartList.OrderDTO?.DeliveryCountry !== '') {
dispatch(getShippingMethodById(token.access_token, deliveryAddressId));
} else {
dispatch(
getShippingMethodById(
token.access_token,
cartList.OrderDTO?.CustomerAddressId,
),
}
}, [cartList]); // Add 'cartList' to your dependency array here
Solution is that you add cartList inside the dependency array.
useEffect(() => {
// All your logic inside
}, [cartList]);
I don't have info about the complete parent-child component structure, but from what I understood with the error I can explain the issue.
You are using [], for useEffect dependency, which means the callback inside useEffect will be triggered only once when the component mounts.
It is possible that when your component mounted, the API call in the parent was not complete, and you have still have undefined in store for cartList.
To check this hypothesis you can add console.log in API response and
inside the useEffect.
What else you can do?
You can not render the child component until you have data from the API call.
Why adding cartList in the dependency array fixed the issue?
By dependency array inside useEffect, your useEffect call back will be
called whenever the values in the dependency array change + on the
mount.
So, at the time of mount of the child component, useEffect's callback will trigger (cartList as undefined), then when the API call is successful and after that data is pushed in state, your child component will rerender and will retrigger the callback inside useEffect with the actual(which you got from API and pushed in store) cartList data.

Resources