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
Related
I need to make an async call after I get some data from a custom hook. My problem is that when I do it causes an infinite loop.
export function useFarmInfo(): {
[chainId in ChainId]: StakingBasic[];
} {
return {
[ChainId.MATIC]: Object.values(useDefaultFarmList()[ChainId.MATIC]),
[ChainId.MUMBAI]: [],
};
}
// hook to grab state from the state
const lpFarms = useFarmInfo();
const dualFarms = useDualFarmInfo();
//Memoize the pairs
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [chainIdOrDefault, lpFarms, dualFarms]);
//Grab the bulk data results from the web
useEffect(() => {
getBulkPairData(pairLists).then((data) => setBulkPairs(data));
}, [pairLists]);
I think whats happening is that when I set the state it re-renders which causes hook to grab the farms from the state to be reset, and it creates an infinite loop.
I tried to move the getBulkPairData into the memoized function, but that's not meant to handle promises.
How do I properly make an async call after retrieving data from my hooks?
I am not sure if I can give you a solution to your problem, but I can give you some hints on how to find out the cause:
First you can find out if the useEffect hook gets triggered too often because its dependency changes too often, or if the components that contains your code gets re-mounted over and over again:
Remove the dependency of your useEffect hook and see if it still gets triggered too often. If so, your problem lies outside of your component.
If not, find out if the dependencies of your useMemo hook change unexpectedly:
useEffect(()=>console.log("chainIdOrDefault changed"), [chainIdOrDefault]);
useEffect(()=>console.log("lpFarms changed"), [lpFarms]);
useEffect(()=>console.log("dualFarms changed"), [dualFarms]);
I assume, this is the most likely reason - maybe useFarmInfo or useDualFarmInfo create new objects on each render (even if these objects contain the same data on each render, they might not be identical). If so, either change these hooks and add some memoization (if you have access to your code) or narrow down the dependencies of your pairLists:
const pairLists = useMemo(() => {
const stakingPairLists = lpFarms[chainIdOrDefault].map((item) => item.pair);
const dualPairLists = dualFarms[chainIdOrDefault].map((item) => item.pair);
return stakingPairLists.concat(dualPairLists);
}, [lpFarms[chainIdOrDefault], dualFarms[chainIdOrDefault]]);
Am using useEffect in a react functional component to fetch data from an external API but it keeps calling the API endpoint on render on the page .
Am looking for a way to stop the useeffect from running on render on the component
Use the dependency array (second argument to the useEffect), so you can specify when you need to run the useEffect.
The problem here is that you have not used the dependency array, so that it executes every time. By adding a dependency array, you specify the changes where you want useEffect to run.
useEffect(()=>{
},[<dependency array: which contains the properties>]);
If you leave the dependency array empty, it will run only once. Therefore if you want the API call to run only once, add an empty array as the second argument to your useEffect. This is your solution.
Like this:
useEffect(()=>{
//Your API Call
},[]);
useEffect is always meant to run after all the changes or render effects are update in the DOM. It will not run while or before the DOM is updated. You may not have given the second argument to useEffect, which if u do not provide will cause the useEffect to execute on each and every change. Assuming you only want to call the API just once when on after the first render you should provide an empty array.
Runs on all updates, see no second argument to useEffect:
useEffect(() => { /* call API */ });
Runs when the prop or state changes, see the second argument:
useEffect(() => { /* call API */ }, [prop, state]);
Runs only once, see the empty second argument:
useEffect(() => { /* call API */ }, []);
I recommend you to read the full documentation about the React useEffect hook.
Here is a easy example of using useEffect
function functionalComponent() {
const [data, setData] = React.useState(null);
React.useEffect(() => {
const url = 'https://randomuser.me/api/?results=10';
fetch(url)
.then(data => {
setData(data);
})
.catch(error => console.error(error))
}, []); // it's necessary to use [] to avoid the re-rendering
return <React.Fragment>
{data !== null && (
<React.Fragment>
{data.results.map(data => (
<div>
{data.gender}
</div>
))}
</React.Fragment>
)}
</React.Fragment>;
}
Maybe in your useEffect implementation you are avoiding the [] dependencies, this is a bit hard to understand if you come from class states. This on hooks review when a state element inside the hook change, for example if you are using an element that always change like a prop that you pass throught another component you might be setting inside the dependencies or another state, if you do not need any dependency just use it empty like the example above. As you can see in the documentation sometimes the dependencies are not used, this might generate an infinite loop.
I have an async function to call before render
(the async function fetches firebase RemoteConfig and I want to use the value when rendering)
Now, I have the following structure
async componentDidMount() {
await remoteConfig.fetch()
}
Problem is that the fetch call is not guaranteed to be called before render (in my test)
How do I make sure to fetch the data before render()?
if you want to execute function before render, I think there are other hooks like getDerivedStateFromProps . But calling api before render is generally not recommended. Using componenDidMount is good, you should structure you code like:
componentDidMount(async () => {const res = await fetch(//your api)})
Once you receive the data, you call this.setState to update your state and react will automatically update the UI
I have put a RESTFUL API inside my react useeffect hook by using axios.get() method, I need this REST API function is fetched and run on each refresh of the page?
Actually when, I am testing my application it is jus running once and no longer it updates
My react useeffect is like below
React.useEffect(() => {
window.scrollTo(0, 0);
document.body.scrollTop = 0;
axios.get('http://127.0.0.1:9000/api/is_logged_in/')
.then(res =>{
console.log(res);
if (res.status!=200) {
throw new Error('Network response was not ok');}
return res;})
.then(res=>{
const value=res.data.res;
set_is_logged_in(value);
}).
catch(error=>{
console.log(error);
});});
I need this API to get re-run and fetched from the sever on each refresh of the page. How to achieve such functionality in reactjs?
Try to understand the concept:
useEffect(() => {
// your logic here which will run only one time when this component mounted
});
useEffect(() => {
// your logic here which will run every time when the variable which is passed in dependency array change
}, []); // Blank Dependency array
useEffect(() => {
// your logic here which will run every time when the variable which is passed in dependency array change
}, [variable1, variable2]); // Dependency array
Explanation:
Giving it an empty array acts like componentDidMount as in, it only
runs once.
Giving it no second argument acts as both componentDidMount and
componentDidUpdate, as in it runs first on mount and then on every
re-render.
Giving it an array as second argument with any value inside, eg ,
[variable1] will only execute the code inside your useEffect hook
ONCE on mount, as well as whenever that particular variable
(variable1) changes.
Reference
I have a situation where I am loading several datasets; the user can choose how many to load. All the datasets are rendered on the same chart. The datasets load individually and asynchronously.
The code is something along the lines of
export const DatasetGraph = ({userSelections}) => {
const [datasets, setDatasets] = useState({});
// when a selection changes, update the data
useEffect(() => {
// while loading a dataset, it is visible but has no data
setDatasets(userSelections.map(selec => ({ dsname: [] })));
// trigger asynchronous loads for each new dataset
userSelections.forEach((dsname) => fetchDataset(dsname));
}, [userSelections]);
const fetchDataset = async (dsname) => {
response = await fetch(url + dsname);
// QUESTION: after the await, will this call the most recent version of
// the callback? Will it use the most recent datasets object or an old
// one saved in the useCallback closure?
updateDataset(dsname, response);
};
// when a fetch returns, update the empty placeholder with the real data
const updateDataset = useCallback(
// For this toy example, we could use a setState function to always
// retrieve the latest `datasets`. However, this callback may use other
// state which is really what this question is about.
(dsname, response) => setDatasets({ ...datasets, dsname: response }),
[datasets]
);
return <Graph data={datasets}></Graph>;
};
I have not yet tried just having each dataset be a React component that doesn't render anything to the DOM, which can then manage its own loading state. That might actually be easier.
useCallback uses the dependencies array to detect changes
The useCallback method uses the dependencies array you pass to it in order to memoize the value of your function. Your function will be recreated every time but not assigned to updateDataset unless one of the dependencies has changed.
You should be wary of using useCallback unless a component below your function is expensive to rerender, otherwise, useCallback won't have much of a positive effect on your application's performance if any positive effect at all.
It works the same way that useMemo does to ensure that your data, in the case of useCallback it is a function, is only updated on your variable when something it depends on has changed.