useEffect fetch request is pulling data twice - javascript

What I Want:
I'm pulling data from an api, then setting the data to state. I've done this inside a useEffect hook, but when I console.log the data afterwards, it's displaying the data twice, sometimes 4 times. I'm at a loss as to why this is happening.
What I've Tried:
console.log within useEffect to see data from source
disabling react developer tools within chrome.
My Code:
// make api call, assign response to data state
const [apiData, setApiData] = useState();
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
fetchData();
}, []);
console.log(apiData);
Result of console.log:

As was mentioned in the other comment this is due to effects being "double invoked" in strict-mode.
A common solution and one I believe has been suggested by the React team (although am struggling to find where I read this) is to use useRef.
// make api call, assign response to data state
const [apiData, setApiData] = useState();
const hasFetchedData = useRef(false);
useEffect(() => {
async function fetchData() {
try {
await fetch('https://restcountries.com/v3.1/all')
.then(response => response.json())
.then(data => setApiData(data));
} catch (e) {
console.error('Error fetching api data', e);
};
};
if (hasFetchedData.current === false) {
fetchData();
hasFetchedData.current = true;
}
}, []);

If you are using React version 18+, StrictMode has a behavior change. Before it wasn't running effects twice, now it does to check for bugs. ( Actually, it does mount-remount, which causes initial render effects to fire twice)
https://reactjs.org/blog/2022/03/29/react-v18.html#new-strict-mode-behaviors

Related

How do I mock this custom React hook with API call

I am new to React testing and I am trying to write the tests for this custom hook which basically fetches data from an API and returns it.
const useFetch = (url) => {
const [response, setResponseData] = useState();
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(response => {
if(response.ok === false){
throw Error('could not fetch the data for that resource');
}
return response.json();
})
.then(data => {
setResponseData(data);
setIsLoading(false);
})
.catch((err) => {
console.log(err.message);
})
}, [url]);
return {response, isLoading};
}
export default useFetch;
Since I am new to this I have tried many solutions but nothing is working and I am getting confused too. Can you provide me a simple test for this so that I can get an idea as to how it is done?
here's an example https://testing-library.com/docs/react-testing-library/example-intro/ using msw library to mock server call.
If you mock the server instead of the hook, it will be more resilient to changes.
Moreover, I suggest you to use a library like react-query or read source code of them and reimplement them if you want to learn to avoid fetching pitfalls

I am fetching data from api in react using useeffect hook.But its getting called multiple times.But i need to call it once

I am fetching data from api in react using useeffect hook.But its getting called multiple times.But i need to call it once.Can anyone tell how to achieve that?
I tried using useeffect hook
const getData = () => {
fetch("https://countriesnow.space/api/v0.1/countries/capital")
.then((response) => {
return response.json();
})
.then((data) => {
scname(data.data);
});
};
useEffect(() => {
getData();
}, []);

useEffect not triggered on state update by its dependency

I am trying to implement a seamless login and trigger another function when login is successful
const [token, setToken] = useState();
useEffect(() => {
async function attemptLogin() {
await fetch('http://localhost:3000/login')
.then(response => response.json())
.then(data => console.log(data.data))
.then(data => setToken(JSON.stringify(data))) // {data: 'Logged in'}
.catch(err => {
console.error('error occured: ', err.message)
});
}
attemptLogin();
}, []);
useEffect(() => {
console.log('should run after token update');
console.log(token); //undefined
// another fetch goes here since we needed to login to get token for API
}, [token]);
So the useEffect with the dependency runs before the one used on mounting. why? Shouldn't it run only when state changes? Does it run on initialization or something? Then why not run when I get the data from my fetch?
The problem is with this piece of code:
.then(data => console.log(data.data))
.then(data => setToken(JSON.stringify(data)))
In .then(), data is not passed on further to the next .then(). That is why the second .then() becomes something like setToken(JSON.stringify(undefined)).
console.log(JSON.stringify(undefined))
As you see that will return undefined so you are doing setToken(undefined).
You probably want to do .then(data => data.data) instead of .then(data => console.log(data.data)), so you are actually returning something.
Note: Do not need the second .then() because there is nothing async here :
.then(data => setToken(JSON.stringify(data.data)))
Also,
useEffect(() => {
console.log('should run after token update');
console.log(token); //undefined
// another fetch goes here since we needed to login to get token for API
}, [token]);
the callback in this useEffect will run on the first render (mount) because token is given a value at that time (although undefined). This counts as a change for React, because earlier the variable did not even exist (a codesandbox demonstrating this).
From the docs:
Does useEffect run after every render? Yes! By default, it runs both after the first render and after every update.
You can check for the first mount using a ref if you do not want this to run on first mount or check the value for undefined using if.
Try this out:
async function attemptLogin() {
let res = await fetch('http://localhost:3000/login')
let jsonData = await res.json();
setToken(jsonData)
}

Page tries to render before data is loaded from API - React

I am pulling data from a cyrpto API that loads data of 250 coins. When I pull only 100 coins, the data is rendered fine. When I see it to 250, the data is rendered before loaded and it gives an error. The data is loaded in console when I log 250 coins.
The data function:
const fetchCoinData = async () => {
setLoading(true);
const fetchedCoinData = await getCoinsData();
setData(fetchedCoinData);
setLoading(false);
};
useEffect(() => {
fetchCoinData();
}, []);
The API call:
export const getCoinsData = async () => {
try {
const response = await Axios.get(
`https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&per_page=100&page=1&sparkline=false&price_change_percentage=1h%2C24h%2C7d`
);
return response.data;
} catch (e) {
console.log(e);
}
};
It would help if you wrapped your axios response in a promise and change your state in the then function since it seems like your state is updating before your API call is over. Something like the followng would help.
await getCoinsData()
.then(fetchedCoinData => {
setData(fetchedCoinData))
setLoading(false)
});

Can't perform a React state update on an unmounted component with useEffect hook

I have
useEffect(() => {
setLoading(true);
axios
.get(url, {params})
.then(data => {
setData(data || []);
setLoading(false);
})
.catch(() => {
showToast('Load data failed!', 'error');
setLoading(false);
});
}, [params]);
It gives me
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.
Ok, the question IS NOT HOW TO SOLVE IT. When I use setLoading(false) after axios promise it works fine but inside of promise (e.g. above) it always gives me warning. Actually I want to know WHY IT HAPPENS SO? Is there anybody who may explain me in a nutshell a flow of code above (the process how code above works with warning) and maybe give some best practices on using hooks.
you need clean up function.
this means you should call function end of useEffect function.
when dependencie is changes (params as your example ) calls that function.
so we would be able controll when component mounts/unmounts
useEffect(() => {
let cancelled = false;
setLoading(false);
async function fetchData() {
try {
const response = await axios.get(url, { params });
if (!cancelled) {
setData(response.data);
setLoading(false);
}
} catch (e) {
if (!cancelled) {
showToast(e.message, "error");
setLoading(false);
}
}
}
fetchData();
// clean up here
return () => {
cancelled = true;
};
}, [params]);
WHY IT HAPPENS SO?
Imagine your request is goes slow, and the component has already unmounted when the async request finishes. this time throws this warning

Categories