Why is this reactjs useEffect function running endlessly? - reactjs

Here is a useEffect hook that I used in reactjs:
Problem: The useEffect calls fetchAllCategories() endlessly. Infact, over 1000 requests until I terminate. I only want it to run on page mount and when I click on the button to change catName state. What could I be doing wrong?
const [catName, setCategories] = useState([]);
const categoryRef = useRef();
useEffect(()=>{
const fetchAllCategories = async () =>{
try{
const res = await axios.get(`${BASE_URL}/category`)
return setCategories(res.data);
}catch(err){
}
}
fetchAllCategories()
}, [catName])
//create new category
const createNewCategory = async ()=>{
const categoryName = {
catName: categoryRef.current.value
}
try{
const response = await axiosPrivate.post(`${BASE_URL}/category`, categoryName, { withCredentials: true,
headers:{authorization: `Bearer ${auth}`}
})
return setCategories([response.data])
}catch(err){
}
}
The button that triggers changes in catName
<button onClick={ createNewCategory} className='button-general'>Create</button>

You trigger the effect to be run on change of catNames, and then change catNames from the effect itself. This results in the endless self-triggering of the effect.
One solution could be to make your effect depend on nothing:
useEffect(() => {
....
}, []);

Thank you all for pointing out the issue to me. Since I now know the issue, I have been able to fix it. All I did was create another state and made the useEffect to depend on it. Then, whenever the button is clicked, I change the state to the opposite of the initial state. This works and doesnt cause endless calls.

If you absolutely need catName as a dependency, this can work for you:
useEffect(() => {
const fetchAllCategories = async () => {
try {
const res = await axios.get(`${BASE_URL}/category`);
return setCategories(res.data);
} catch (err) {}
};
fetchAllCategories();
}, [JSON.stringify(catName)]);

This is happening because you update the catName state inside the useEffect using setCategories(res.data) this keeps triggering your useEffect thus an infinite loop

You programmed it to run endlessly.
REASON
See, this React hook useEffect has two parameters:
Effect, the callback function which is called whenever the component renders.
List of Dependencies on which the useEffect hook depends and re-renders if any such dependency is updated.
Now, in your case, you've passed catName as a dependency, whose state is updated when the button is clicked. This causes useEffect to call the callback function which calls fetchAllCategories() which also update catName. And this causes an endless loop.
SOLUTION
Just remove the dependency from useEffect as:
useEffect(() {
...
}, []);
Now, updating catName won't cause the effect (The Callback function) to be called endlessly.

Related

The setItem function is working fine in Reactjs but localStorage.getItem is not working

In the code I change some recipe content and it saves but when I refresh my page it resets everything and the changes are gone
Here is my code
const LOCAL_STORAGE_KEY = "cookingWithKyle.recipes"
function App() {
const [selectedRecipeId, setSelectedRecipeId] = useState()
const [recipes, setRecipes] = useState(sampleRecipe)
const selectedRecipe = recipes.find(
(recipe) => recipe.id === selectedRecipeId
)
useEffect(() => {
const recipeJSON = localStorage.getItem(LOCAL_STORAGE_KEY)
if (recipeJSON) setRecipes(JSON.parse(recipeJSON))
}, [])
useEffect(() => {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(recipes))
}, [recipes])
AFAIK, in React useEffect hook's callback is run asynchronously. So, you cannot control which callback is processed first and end before another.
But here, the main reason of your issue is, even if you have recipes as a dependency in your second useEffect, second useEffect's callback will also be fired on initial mount (on page reload), thus in your code you set a value (probably undefined) to your localStorage and then try to get a value, which was already gone.
So, try to run your second useEffect's callback with a condition:
useEffect(() => {
if (recipes) {
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(recipes))
}
}, [recipes])
For information, React fires the useEffect hooks callback when it compares dependency list in an array with a previous render (so, try not to use a value of object type as a dependency, otherwise your callback will be fired in every render) and if it sees the difference, it fires useEffect's callback, including the initial render.

useEffect dependency cause an infinite loop

function Reply({ id, user }) {
const [data, setData] = useState([]);
const [replyText, setReplyText] = useState("");
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, [data]); <---- ** problem ** with data(dependency),
infinite request(call) fetchData()
...
}
what's the reason for infinite loop if there's a dependency.
as far as i know, when dependency(data) change, re-render.
but useEffect keep asking for data(axios.get(~~)).
if i leave a comment, i can see normally the latest comments, but the network tab(in develop tools) keeps asking for data(304 Not Modified, under image)
There's an infinite loop because that code says "If data changes, request information from the server and change data." The second half of that changes data, which triggers the first half again.
You're not using data in the callback, so it shouldn't be a dependency. Just remove it:
useEffect(() => {
async function fetchData() {
const response = await _axios.get("/reply/" + id);
setData(response.data);
}
fetchData();
}, []);
// ^^−−−−−−−−−− don't put `data` here
That gives you a blank dependency array, which will run the effect only when the component first mounts. (If you want to run it again after mount, use a different state member for that, or define fetchData outside the effect and use it both in the effect and at the other time you want to fetch data.)
Side note: Nothing in your code is handling rejections from your fetchData function, which will cause "unhandled rejection" errors. You'll want to hook up a rejection handler to report or suppress the error.
You are using setData after the response which causes the data to change and hence the useEffect(() => {<>your code<>} ,[data]) to fire again.
use useEffect(() => {<>your code<>},[]) if you want to execute the AJAX call only once after component mounting
or
use useEffect(() => {<>your code<>}) without the dependency if you want to execute the AJAX call after the component mount and after every update
Dependencies argument of useEffect is useEffect(callback, dependencies)
Let's explore side effects and runs:
Not provided: the side-effect runs after every rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs after EVERY rendering
});
}
An empty array []: the side-effect runs once after the initial rendering.
import { useEffect } from 'react';
function MyComponent() {
useEffect(() => {
// Runs ONCE after initial rendering
}, []);
}
Has props or state values [prop1, prop2, ..., state1, state2]: the side-effect runs only when any dependency value changes.
import { useEffect, useState } from 'react';
function MyComponent({ prop }) {
const [state, setState] = useState('');
useEffect(() => {
// Runs ONCE after initial rendering
// and after every rendering ONLY IF `prop` or `state` changes
}, [prop, state]);
}

I Got Stuck in infinite loop in react.js. How to resolve this?

I Got Stuck in an infinite loop in react.js. How to resolve this?
useEffect(() => {
fetch("https://react-http-d55a9-default-rtdb.firebaseio.com/todo.json")
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
setUsersList((prev) => [...prev]); //cause of infinite loop
});
}, [usersList]);
You are having an infinite loop because your useEffect array of dependencies has usersList on it and at the same time you are updating this variable inside your useEffect function. So your useEffect runs when the component mounts which updates your usersList which makes the useEffect run again which again updates your usersList which makes it run again and so on...
To fix this, remove usersList from the array of dependencies and have an empty array instead: []. If you do this your useEffect will run once, when your component mounts.
The dependency list passed to useEffect determines when the effect should run again. The infinite loop is happening because this effect causes usersList to change, which triggers the effect to run again.
Since this effect doesn't use any other variables, it doesn't need anything in its dependency list:
useEffect(() => {
fetch(...)
// ...
}, []); // leave this empty, so the effect only runs when the component mounts
If your URL depended on a prop or something else, then you want it in the dependency list:
useEffect(() => {
fetch(`https://example.com/todo/${props.id}`)
.then(...)
// Since the URL depends on the id prop, the effect should re-run if it changes
}, [props.id]);
According to question asked, you want the userList to be watched everytime it updates. What we can do is define one more state variable as mentioned in the code as isFetched or if you are using redux you can put that over there, because if we just watch the userList variable then it caughts up in infinite loop as setting the userList is happening in useEffect itself. With the help of isFetched, we can manage when to call the api and whenever the flag is false it calls the api.
Right now in the code i have put one more state variable as setCount, as i didn't know how many times you want to call your api. So you can put your condition there and stop the call when your condition satisfies.
function App() {
const [userList, setUserList] = useState([]);
const [isFetched, setIsFetched] = useState(false);
const [, setCount] = useState(3);
const callApiPending = useCallback(()=>{
fetch("https://react-http-d55a9-default-rtdb.firebaseio.com/todo.json")
.then((response) => response.json())
.then((json) => {
setUserList((prev) => [...prev, ...json]);
setCount((cnt) => {
if(cnt - 1 === 0){
setIsFetched(true);
}
return cnt - 1;
});
});
}, []);
useEffect(() => {
if (!isFetched) {
callApiPending();
}
}, [isFetched, userList, callApiPending]);
return <div>Executing....</div>;
}
You ran fetch if usersList changes. Even if userList content is the same as previous content, javascript interpret as it changed. Try this one.
[1,2,3] == [1,2,3]
may return false. You can use a flag which is used to check whether or not to get data instead of using array.

Infinite re-render in functional react component

I am trying to set the state of a variable "workspace", but when I console log the data I get an infinite loop. I am calling the axios "get" function inside of useEffect(), and console logging outside of this loop, so I don't know what is triggering all the re-renders. I have not found an answer to my specific problem in this question. Here's my code:
function WorkspaceDynamic({ match }) {
const [proposals, setProposals] = useState([{}]);
useEffect(() => {
getItems();
});
const getItems = async () => {
const proposalsList = await axios.get(
"http://localhost:5000/api/proposals"
);
setProposals(proposalsList.data);
};
const [workspace, setWorkspace] = useState({});
function findWorkspace() {
proposals.map((workspace) => {
if (workspace._id === match.params.id) {
setWorkspace(workspace);
}
});
}
Does anyone see what might be causing the re-render? Thanks!
The effect hook runs every render cycle, and one without a dependency array will execute its callback every render cycle. If the effect callback updates state, i.e. proposals, then another render cycle is enqueued, thus creating render looping.
If you want to only run effect once when the component mounts then use an empty dependency array.
useEffect(() => {
getItems();
}, []);
If you want it to only run at certain time, like if the match param updates, then include a dependency in the array.
useEffect(() => {
getItems();
}, [match]);
Your use of useEffect is not correct. If you do not include a dependency array, it gets called every time the component renders. As a result your useEffect is called which causes setProposals then it again causes useEffect to run and so on
try this
useEffect(() => {
getItems();
} , []); // an empty array means it will be called once only
I think it's the following: useEffect should have a second param [] to make sure it's executed only once. that is:
useEffect(() => {
getItems();
}, []);
otherwise setProposal will modify the state which will trigger a re-render, which will call useEffect, which will make the async call, which will setProposal, ...

How to fix multiple call fetch data in forEach() using React Hooks

In react Hooks, I am trying to fetch data from the API array but in the Foreach function, the API call causes infinity.
How to fix this?
const [symbols, setSymbols] = useState([]);
getPortfolioSymbolList(portfolio_name).then(data => data.json()).then(res => {
res.forEach((symbol_data)=>{
fetchPrice(symbol_data.symbol).then(price => {
setSymbols(price);
});
})
}
function fetchPrice(symbol){
const price = fetch(`api_url`)
.then(chart => chart.json())
return price;
}
Here, call fetchPrice() causes in infinite.
Setting the state will always cause a rerender
What happens in your code is the request is made and then the data is set causing a rerender. Then because of the rerender the request is made again and sets the state again and causes the rerender again.
If you have a request for data you probably want to put a React.useEffect so it only requests once.
React.useEffect(() => {
/* your data request and data set */
}, []); // the [] will only fire on mount.
Is is because your setSymbols call inside forEach makes component rerender (reload) - it means that all of your main component function is call again and again... getPortfolioSymbolList too. You have to use useEffect hook to resolve this problem. Your getPortfolioSymbolList() API call should be inside useEffect.
https://reactjs.org/docs/hooks-effect.html
PROBLEM
Your first symbol is updated in your API call, which triggers a re-render of the component calling the API call to go on an infinite loop.
SOLUTION
Wrap your API in your useEffect. The function inside your useEffect will only be called once. See useEffect docs here
You need to use for await of to loop asynchronously. forEach can't loop asynchronously. See for await of docs here
Update your symbols once all the data is collected.
function Symbols() {
const [symbols, setSymbols] = useState([]);
React.useEffect(() => {
async function fetchSymbols() {
const portfolio = await getPortfolioSymbolList(portfolio_name);
const jsonPortfolios = await data.json();
const symbols = [];
for await (let jsonPortfolio of jsonPortfolios) {
const price = await fetchPrice(jsonPortfolio.symbol);
symbols.push(price);
}
setSymbols(symbols);
}
fetchSymbols();
}, []);
return /** JSX **/
}

Resources