useEffects keeps looping for infinity - reactjs

Pardon me if this is a silly question. Im a new react learner. Im trying using a create react app. I am using a custom hook for API handling only. Now I want the useEffect to run only when the data changes. Thats why I put it in dependency. But yet it keeps rendering for infinity. What is the problem? Or how should I handle this?
Thank you.
import { useCallback, useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
const getDogCollection = useCallback(() => {
fetch(url, options)
.then((res) => res.json())
.then((result) => {
console.log(data, "----DI---", result);
setData(result);
});
}, []);
useEffect(() => {
getDogCollection();
}, [data]);
return data;
};

You'll just want url and options to be the dependencies, not data (because you set it in the effect!).
import { useEffect, useState } from "react";
export const useAPI = (url, options) => {
const [data, setData] = useState([]);
useEffect(() => {
fetch(url, options)
.then((res) => res.json())
.then(setData);
// TODO: add error handling...
}, [url, options]);
return data;
};
However, you'll probably just want to look at swr instead of writing this hook yourself.

It's because you've given data as one of the dependencies, but the function called in your useEffect updates data, so it then runs again.
You can change it to the length, like this, and it should work:
useEffect(() => {
getDogCollection();
}, [data.length]);

Related

Explain useUserSearch hook for ReactJS that uses axios

I'm trying to wrap my head around this code. It essentially sends two params (page number + query) to a server, then gets the results and saves them in a state.
However, I don't understand the cancel, axios.CancelToken...etc part within useEffect. I would appreciate if it someone explains this thoroughly.
import axios from "axios";
import { useEffect, useState } from "react";
export default function useUserSearch(query, pageNumber) {
const [loading, setLoading] = useState(true);
const [error, setError] = useState(false);
const [users, setUsers] = useState([]);
const [hasMore, setHasMore] = useState(false);
useEffect(() => {
setLoading(true);
setError(false);
let cancel;
axios
.get("https://someserver.com/users/search/", {
params: { q: query, page: pageNumber },
cancelToken: new axios.CancelToken((c) => (cancel = c)),
})
.then((res) => {
setUsers((prevState) => [...prevState, ...res.data.users]);
setHasMore(users.length >= res.data.total);
setLoading(false);
console.log(res.data);
})
.catch((e) => {
if (axios.isCancel(e)) return;
console.log(e);
setError(true);
});
return () => cancel();
}, [query, pageNumber, users]);
return { loading, error, users, hasMore };
}
The cancel token is used to cancel a request, but it is using deprecated API. Axios has newer API for that too.
As far as useEffect is concerned, the cancellation happens in the function which is returned by useEffect, that function runs when component unmounts and when any of its dependencies change.
Idea is for example if I have an ongoing query for keyword "Nick" and suddenly query was changed to "John", we are not interested in "Nick" query anymore so we can cancel it. Also if we don't cancel it and execute both queries "Nick" may arrive later, and overwrite result for "John".

Repeat Fetching with UseEffect and SetTimeOut on React.js

I want to make Component that Fetching Data from REST API with repeatedly.
I noticed There is some problem with setInterval and React.js.
so I coded recursive way with setTimeout and useEffect.
It seems work well though, I am curious about necessity of clearTimeout.
Is there no performance Issue cause of no clearTimeout or else?
ps: Sorry for my poor English.
import { useEffect, useState } from "react";
import axios from "axios";
function RepeatFetch() {
const [data, setData] = useState(null);
const repeatFetchData = async () => {
try {
const response = await axios.get(
"https://api.foo.com/"
);
setData(response.data);
} catch (e) {
}
setTimeout(repeatFetchData, 3000);
};
useEffect(() => {
responseFetchData();
// return () => clearTimeout(timer); // Do I need clearTimeout? then, how?
}, []);
return (
<div>
{data}
</div>
);
}
export default RepeatFetch;

Can't perform a React state update on an unmounted component when using custom hook for fetching data

I am having this warning in my console whenever I try to run this custom hook I made for fetching data, here is how it looks like:
import { useState, useEffect } from "react";
import axios from "axios";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
axios
.get(url)
.then((response) => {
setData(response.data);
})
.catch((err) => {
setError(err);
});
}, [url]);
return { data, error };
};
export default useFetch;
And this is the full warning message:
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.
Does anyone know how to fix this?
You need to make sure the component is still mounted before trying to update the state:
import { useState, useEffect } from "react";
import axios from "axios";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const isMounted=useRef(null)
useEffect(()=>{ isMounted.current=true; return()=>{isMounted.current=false}},[])
useEffect(() => {
axios
.get(url)
.then((response) => {
if (isMounted.current)
{setData(response.data);}
})
.catch((err) => {
if (isMounted.current)
{setError(err);}
});
}, [url]);
return { data, error };
};
export default useFetch;
Set a mutable value to true right after the component mounts, and set it to false when it is going to unmount.
Before every setState check if it is still mounted.

Infinate loop with fetching data in UseEffect

Having a weird issue where by i am fetching some data from my local api, and it is infinately calling it for some strange reason:
import React, { useState, useEffect } from 'react';
const Users = () => {
const [users, setUsers] = useState([])
const fetchUsers = async () => {
try {
await fetch('http://localhost:3001/users')
.then(response => response.json())
.then(data => setUsers(data));
}
catch(ex) {
console.error('ex:', ex);
}
}
useEffect(() => {
fetchUsers();
}, [users])
return <div>xxx</div>
}
export default Users;
If I console.log(data) instead of setUsers(data), then all seems to be fine and the console log only outputs 1 set of information.
I am unsure what I am doing wrong. Any ideas?
useEffect(() => {
fetchUsers();
}, [users])
should be:
useEffect(() => {
fetchUsers();
}, [])
The first will fetch users every time user changes. then it changes the users object with the results which causes the infinite loop.
The fix instead only calls it once on mount.
Your effect:
useEffect(() => {
fetchUsers();
}, [users]);
will be executed whenever someone changes users object. In this case, the first time you will call useEffect, and fetch data from API, when you receive data from the backend you will update users object and trigger an infinite loop.
You can solve problem with:
useEffect(() => { fetchUsers(); }, []);

How do I fix "useEffect has a missing dependency" in custom hook

I'm using a custom hook to get pull some data in from an API for use across a set of React function components. However, esLint throws up a lovely warning:
React Hook useEffect has a missing dependency: 'fetchFromAPI'. Either
include it or remove the dependency array.
I didn't think it's a dependency, as it's inside useFetch() itself. I need to do it as I'm using await. What am I doing wrong? Is it ok to just turn off the warning for this line? Or is there a more canonical syntax I should be using?
function useFetch (url) {
const [data, setData] = useState(null);
async function fetchFromAPI() {
const json = await( await fetch(url) ).json();
setData(json);
}
useEffect(() => {fetchFromAPI()},[url]);
return data;
};
export {
useFetch
};
I suggest you to define async function inside useEffect itself:
function useFetch (url) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchFromAPI() {
const json = await( await fetch(url) ).json();
setData(json);
}
fetchFromAPI()
},[url]);
return data;
};
You can take a look at valid example from doc faqs which uses async function inside useEffect itself:
function ProductPage({ productId }) {
const [product, setProduct] = useState(null);
useEffect(() => {
// By moving this function inside the effect,
// we can clearly see the values it uses.
async function fetchProduct() {
const response = await fetch('http://myapi/product' + productId);
const json = await response.json();
setProduct(json);
}
fetchProduct();
}, [productId]); // ✅ Valid because our effect only uses productId
// ...
}
Declare it outside your custom effect passing url as parameter and return the json to be setted inside useEffect
async function fetchFromAPI(url) {
const json = await( await fetch(url) ).json();
return json
}
function useFetch (url) {
const [data, setData] = useState(null);
useEffect(() => {
setData(fetchFromAPI(url))
},[url]);
return data;
};
Or directly inside useEffect
function useFetch (url) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchFromAPI() {
const json = await( await fetch(url) ).json();
return json
}
setData(fetchFromAPI())
},[url]);
return data;
};
Just move your function inside the useEffect, and everything will be fine:
import { useState, useEffect } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
useEffect(() => {
async function fetchFromAPI() {
const json = await (await fetch(url)).json();
setData(json);
}
fetchFromAPI();
}, [url]);
return data;
}
export { useFetch };
https://codesandbox.io/s/clever-cdn-8g0me
async function fetchFromAPI(url) {
return ( await fetch(url) ).json();
}
function useFetch (url) {
const [data, setData] = useState(null);
useEffect(() => {
fetchFromAPI(url).then(setData);
}, [url, setData, fetchFromAPI]);
return data;
};
export {
useFetch
};
You can change a little and then extract fetchFromAPI, for not being created every time useFetch calls, also it's good for single responsibility.
If you understand this code very well of course you can either turn off linting for this current line or you can add the rest setData and fetchFromAPI params. And exactly in this order. Because on re-render params comparison start from first param to last, and it's better place most changed param in first place, for not checking not changed param every time the next one is changed, so if first changed, useEffect don't need to check the others and call passed function earlier

Resources