React- Issue with infinite loop in useEffect - reactjs

I have a useEffect() that fetches the data through axios, I want that to render only one time so I passed an array. Everything works fine, but the problem is whenever I try to sort the items, the second useEffect fires just followed by the first useEffect, which is causing the component to fetch the items all over again and again.
const [products, setProducts] = useState([]);
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, [products]);

This is because you passed an array containing your products state, rather than an empty array, which will fire useEffect on state change (for products state specifically). Try changing your code to an empty array:
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, []);
As #skyboyer mentioned below, it is good to note that state is not updated in a synchronous manner. Therefor, console.log(products) will not reflect an accurate value for your state when useEffect runs.
It is okay to use multiple useEffect hooks. If you would like to view your updated state in the console, or do some other work with it, you could add another useEffect hook and pass your state into the array:
useEffect(() => {
console.log(products);
}, [products]);

Since products is in the useEffect dependency array, it is going to run every time there are changes made to the products state. getProducts() runs setProducts which then in turn is going to trigger the use effect again. Using an empty array in the useEffect will tell it to only run when the component is mounted.
Like this:
useEffect(() => {
const getProducts = async () => {
return await axios
.get('/getAllProducts')
.then((response) => {
setProducts(response.data);
console.log(products);
})
.catch((e) => {
console.log(e);
});
};
getProducts();
}, []);

Related

React useEffect prevent on initial mount

i have five a snapshot listener in useEffect and i have another call api to get data from firestore and update state
but I am facing a problem is every initial mount all listener got called , my goal is i want to all listener called only when document changed
i tried with useRef it works but listener do not trigger
As you can see in the example below, onSnapshot is printed during the initial mounted
useEffect(() => {
if (isFirstMount.current) return;
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
someFirestoreAPICall.onSnapshot((snap) => {
//called every initial mount
});
}, []);
useEffect(() => {
if (isFirstMount.current) {
isFirstMount.current = false;
return;
}
}, []);
useEffect(() => {
const fetchData = async () => {
setLoading(true);
let snap = await someFirestoreAPICall.get();
setData(snap.docs.map((doc) => ({ ...doc.data(), id: doc.id })));
setLoading(false);
};
fetchData();
}, []);
Codesandbox
You can use a condition inside your useEffect block as you are doing, I think. But perhaps useState would be more appropriate here than useRef.
eg:
const [state, setState] = useState(null)
useEffect(()=>{
if (state) {
// do something
}
}, [state])
The useEffect will run on mount and every time you change the value of state, but code inside the condition will only run if you change the state to a truthy value.

Using useState inside custom react hooks causes renderings

I created a custom react hook to fetch data. Unfortunately when the useGetData gets called from a component, the component will render for each useState that is performed inside the hook. How can I prevent the additional renderings?
export default function useGetData(
setData: (fetchData) => void
): [(id: string) => void, boolean, boolean] {
const [loadingData, setLoading] = useState(false)
const [successData, setSuccess] = useState(false)
const getData = (id: string) => {
if (!id || !Number(id)) {
setData(null)
return
}
setSuccess(false)
setLoading(true)
Api.getData(Number(id))
.then((response) => {
setSuccess(true)
setData(response)
})
.finally(() => {
setLoading(false)
})
}
return [getData, loadingData, successData]
}
React < 18.x does not do automatical batching for promise callbacks, timeouts, intervals, .... It does it only for event handlers registered via JSX props and in lifecycle methods / effects.
Solution 1: You can use "unstable" API ReactDOM.unstable_batchedUpdates which, despite being marked as unstable, has been working for years just fine:
Api.getData(Number(id))
.then((response) => ReactDOM.unstable_batchedUpdates(() => {
setSuccess(true)
setData(response)
}))
.finally(() => {
setLoading(false)
})
The function will batch all state updates that are executed inside of passed function (synchronous); in the example setSuccess and setData; so that that they will be merged into single re-render, just like React does it in onClick and other event handlers.
Other possibilities: I can think of 2 other options that I don't really like but might work for you:
You can merge all your state into single state variable so that you have single setter.
Instead of calling setSuccess(true); setData(response); in promise callback, you can do it in an effect by introducing another state variable.
const [response, setResponse] = useState();
useEffect(() => {
if (!response) return;
setSuccess(true);
setData(response);
}, [response]);
...
Api.getData(Number(id))
.then(setResponse)
.finally(() => {
setLoading(false)
})

React useEffect to run on both component mounting and dependency change

I want to know how to run the useEffect side effect in both component mounting and a dependent value change. Currently I'm using two useEffects to achieve this like this.
useEffect(() => {
let isMounted = true;
const getUsers = async () => {
try {
const userResponse = await api.get('/users');
if (isMounted) { setUsers(userResponse.data); }
} catch (error) {
console.log(error);
}
};
getUsers();
}, []);
useEffect(() => {
let isMounted = true;
const getUsers = async () => {
try {
const userResponse = await api.get('/users');
if (isMounted) { setUsers(userResponse.data); }
} catch (error) {
console.log(error);
}
};
getUsers();
}, [netInfo]);
Is there anyway to achieve this using one useEffect?
Runs when the component is mounted for the first time and on every re-render
useEffect(() => {})
Runs when the component is mounted for the first time alone
useEffect(() => {}, [])
Runs when the component is mounted for the first time and whenever the someDependency's value changes .
useEffect(() => {}, [someDependency])
You can remove the first useEffect .

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(); }, []);

useEffect infinite loop network request

I am getting infinite requests on my network, and it's due to my useEffect. I know that the problem is because I am putting in the brackets as the second argument the 'posts' and the 'setPost' inside the useEffect function, but I need the page to render whenever I add a new post, so the 'posts' must be inside the brackets.
function Home() {
const {userData, setUserData} = useContext(userContext)
const [posts, setPost] = useState([])
const [createPost, setCreatePost] = useState('')
const handleToken = () => {
localStorage.removeItem('auth-token')
}
const token = localStorage.getItem("auth-token");
const handleOnSubmit = (e) => {
e.preventDefault()
axios.post('http://localhost:5000/posts', {textOfThePost: createPost}, {
headers: { 'auth-token': token },
})
.then((res) => {setCreatePost("")})
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data)
})
}
useEffect(() => {
}, [posts])
If you're doing setPost inside useEffect, I assume posts being changed, and you've added posts as dependency in useEffect, Of course which will re-call and it goes infinite loop. Make sure when do you want to call posts API.
const [posts, setPost] = useState([])
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data) // Which will change `posts`
})
}, [posts]) // this will trigger useEffect and It goes infinite loop
// Change it to
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data) // Which will change `posts`
})
}, []) -> Which call only one time
This useEffects gets called everytime posts changes, and inside the useEffect you're changing posts value, so you got into an recursive loop.
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data)
})
}, [posts])
If you want it to get called only once, you should leave the empty array in your effect, so it will only get called once when your component is mounted.
useEffect(() => {
axios.get('http://localhost:5000/posts')
.then(res => {
setPost(res.data)
})
}, [])

Resources