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.
Hi I'm trying to make a twitter clone app. I am using React on the client side and Express on the server side and PostgreSQL as my database. So here's the problem, I'm trying to use the useEffect like this:
const [tweets, setTweets] = useState([]);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
}, [tweets]);
I have no idea why it's looping infinite times, am I using it correctly though? I want the tweets to be updated every time I post a tweet. It's working fine but it's running infinite times. I just want it to re-render if a tweet got posted.
Here's my server code for getting all the posts:
async all(request: Request, response: Response, next: NextFunction) {
return this.postRepository.find({
relations: ["user"],
order: {
createdAt: "DESC",
},
});
}
The problem is every time you change the tweets it executes useEffect and changes the tweets and so long and so forth, so it's natural that it loops infinitely, the solution is to add a trigger that you set to true when a tweet gets posted, so the solution would be like this
const [tweets, setTweets] = useState([]);
const [isFetching, setIsFetching] = useState(false);
const getTweets = async () => {
const res = await api.get("/posts", {
headers: { token: localStorage.token },
});
setTweets(res.data);
};
useEffect(() => {
getTweets();
setIsFetching(false);
}, [isFetching]);
and set some logic to use setIsFetching(true) in order to execute the useEffect
PS: if you use an empty array in useEffect, it would execute only when the component is mounted (at the start)
useEffect(() => {
getTweets();
}, [tweets]); // [tweets means that hook works every time 'tweets' state changes]
so your getTweets function set tweets => as tweets are changed hook works again => call getTweets => ... = infinite loop
if you want to download tweets, use empty array instead - hook will work once then
Pass empty array as a second arg for calling it once otherwise for changing it on every tweet change it will re-trigger, so whenever state will change only then it will be re-rendered like Tarukami explained. One thing you can do is check the length like mentioned below so not to compare the whole object but just the length
useEffect(() => {
getTweets();
}, [tweets.length]);
This might raise an error react-hooks/exhaustive-deps lint error (that's a bypass you can use it).
But if you want more tighter check you can compare the ids on each re-render (create a hash/key/id from all element in the array and compare them on each render) like so [tweet id here]) // Only re-subscribe if id changes
I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!
I'm building a basic Slack clone. So I have a "Room", which has multiple "Channels". A user subscribes to all messages in a Room, but we only add them to the current message list if the new message is part of the user's current Channel
const [currentChannel, setCurrentChannel] = useState(null);
const doSomething = (thing) => {
console.log(thing, currentChannel)
}
useEffect(() => {
// ... Here I have a call which will grab some data and set the currentChannel
Service.joinRoom(roomId).subscribe({
next: (x) => {
doSomething(x)
},
error: (err: any) => { console.log("error: ", err) }
})
}, [])
I'm only showing some of the code here to illustrate my issue. The subscription gets created before currentChannel gets updated, which is fine, because we want to listen to everything, but then conditionally render based on currentChannel.
The issue I'm having, is that even though currentChannel gets set correctly, because it was null when the next: function was defined in the useEffect hook, doSomething will always log that currentChannel is null. I know it's getting set correctly because I'm displaying it on my screen in the render. So why does doSomething get scoped in a way that currentChannel is null? How can I get it to call a new function each time that accesses the freshest state of currentChannel each time the next function is called? I tried it with both useState, as well as storing/retrieving it from redux, nothing is working.
Actually it is related to all async actions involving javascript closures: your subscribe refers to initial doSomething(it's recreated on each render) that refers to initial currentChannel value. Article with good examples for reference: https://dmitripavlutin.com/react-hooks-stale-closures/
What can we do? I see at least 2 moves here: quick-n-dirty and fundamental.
We can utilize that useState returns exact the same(referentially same) setter function each time and it allows us to use functional version:
const doSomething = (thing) => {
setCurrentChannel(currentChannelFromFunctionalSetter => {
console.log(thing, currentChannelFromFunctionalSetter);
return currentChannelFromFunctionalSetter;
}
}
Fundamental approach is to utilize useRef and put most recent doSomething there:
const latestDoSomething = useRef(null);
...
const doSomething = (thing) => { // nothing changed here
console.log(thing, currentChannel)
}
latestDoSomething.current = doSomething; // happens on each render
useEffect(() => {
Service.joinRoom(roomId).subscribe({
next: (x) => {
// we are using latest version with closure on most recent data
latestDoSomething.current(x)
},
I've been playing around with the new hook system in React 16.7-alpha and get stuck in an infinite loop in useEffect when the state I'm handling is an object or array.
First, I use useState and initiate it with an empty object like this:
const [obj, setObj] = useState({});
Then, in useEffect, I use setObj to set it to an empty object again. As a second argument I'm passing [obj], hoping that it wont update if the content of the object hasn't changed. But it keeps updating. I guess because no matter the content, these are always different objects making React thinking it keep changing?
useEffect(() => {
setIngredients({});
}, [ingredients]);
The same is true with arrays, but as a primitive it wont get stuck in a loop, as expected.
Using these new hooks, how should I handle objects and array when checking weather the content has changed or not?
Passing an empty array as the second argument to useEffect makes it only run on mount and unmount, thus stopping any infinite loops.
useEffect(() => {
setIngredients({});
}, []);
This was clarified to me in the blog post on React hooks at https://www.robinwieruch.de/react-hooks/
Had the same problem. I don't know why they not mention this in docs. Just want to add a little to Tobias Haugen answer.
To run in every component/parent rerender you need to use:
useEffect(() => {
// don't know where it can be used :/
})
To run anything only one time after component mount(will be rendered once) you need to use:
useEffect(() => {
// do anything only one time if you pass empty array []
// keep in mind, that component will be rendered one time (with default values) before we get here
}, [] )
To run anything one time on component mount and on data/data2 change:
const [data, setData] = useState(false)
const [data2, setData2] = useState('default value for first render')
useEffect(() => {
// if you pass some variable, than component will rerender after component mount one time and second time if this(in my case data or data2) is changed
// if your data is object and you want to trigger this when property of object changed, clone object like this let clone = JSON.parse(JSON.stringify(data)), change it clone.prop = 2 and setData(clone).
// if you do like this 'data.prop=2' without cloning useEffect will not be triggered, because link to data object in momory doesn't changed, even if object changed (as i understand this)
}, [data, data2] )
How i use it most of the time:
export default function Book({id}) {
const [book, bookSet] = useState(false)
const loadBookFromServer = useCallback(async () => {
let response = await fetch('api/book/' + id)
response = await response.json()
bookSet(response)
}, [id]) // every time id changed, new book will be loaded
useEffect(() => {
loadBookFromServer()
}, [loadBookFromServer]) // useEffect will run once and when id changes
if (!book) return false //first render, when useEffect did't triggered yet we will return false
return <div>{JSON.stringify(book)}</div>
}
I ran into the same problem too once and I fixed it by making sure I pass primitive values in the second argument [].
If you pass an object, React will store only the reference to the object and run the effect when the reference changes, which is usually every singe time (I don't now how though).
The solution is to pass the values in the object. You can try,
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [Object.values(obj)]);
or
const obj = { keyA: 'a', keyB: 'b' }
useEffect(() => {
// do something
}, [obj.keyA, obj.keyB]);
If you are building a custom hook, you can sometimes cause an infinite loop with default as follows
function useMyBadHook(values = {}) {
useEffect(()=> {
/* This runs every render, if values is undefined */
},
[values]
)
}
The fix is to use the same object instead of creating a new one on every function call:
const defaultValues = {};
function useMyBadHook(values = defaultValues) {
useEffect(()=> {
/* This runs on first call and when values change */
},
[values]
)
}
If you are encountering this in your component code the loop may get fixed if you use defaultProps instead of ES6 default values
function MyComponent({values}) {
useEffect(()=> {
/* do stuff*/
},[values]
)
return null; /* stuff */
}
MyComponent.defaultProps = {
values = {}
}
Your infinite loop is due to circularity
useEffect(() => {
setIngredients({});
}, [ingredients]);
setIngredients({}); will change the value of ingredients(will return a new reference each time), which will run setIngredients({}). To solve this you can use either approach:
Pass a different second argument to useEffect
const timeToChangeIngrediants = .....
useEffect(() => {
setIngredients({});
}, [timeToChangeIngrediants ]);
setIngrediants will run when timeToChangeIngrediants has changed.
I'm not sure what use case justifies change ingrediants once it has been changed. But if it is the case, you pass Object.values(ingrediants) as a second argument to useEffect.
useEffect(() => {
setIngredients({});
}, Object.values(ingrediants));
As said in the documentation (https://reactjs.org/docs/hooks-effect.html), the useEffect hook is meant to be used when you want some code to be executed after every render. From the docs:
Does useEffect run after every render? Yes!
If you want to customize this, you can follow the instructions that appear later in the same page (https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects). Basically, the useEffect method accepts a second argument, that React will examine to determine if the effect has to be triggered again or not.
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes
You can pass any object as the second argument. If this object remains unchanged, your effect will only be triggered after the first mount. If the object changes, the effect will be triggered again.
I'm not sure if this will work for you but you could try adding .length like this:
useEffect(() => {
// fetch from server and set as obj
}, [obj.length]);
In my case (I was fetching an array!) it fetched data on mount, then again only on change and it didn't go into a loop.
If you include empty array at the end of useEffect:
useEffect(()=>{
setText(text);
},[])
It would run once.
If you include also parameter on array:
useEffect(()=>{
setText(text);
},[text])
It would run whenever text parameter change.
I often run into an infinite re-render when having a complex object as state and updating it from useRef:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients({
...ingredients,
newIngedient: { ... }
});
}, [ingredients]);
In this case eslint(react-hooks/exhaustive-deps) forces me (correctly) to add ingredients to the dependency array. However, this results in an infinite re-render. Unlike what some say in this thread, this is correct, and you can't get away with putting ingredients.someKey or ingredients.length into the dependency array.
The solution is that setters provide the old value that you can refer to. You should use this, rather than referring to ingredients directly:
const [ingredients, setIngredients] = useState({});
useEffect(() => {
setIngredients(oldIngedients => {
return {
...oldIngedients,
newIngedient: { ... }
}
});
}, []);
If you use this optimization, make sure the array includes all values from the component scope (such as props and state) that change over time and that are used by the effect.
I believe they are trying to express the possibility that one could be using stale data, and to be aware of this. It doesn't matter the type of values we send in the array for the second argument as long as we know that if any of those values change it will execute the effect. If we are using ingredients as part of the computation within the effect, we should include it in the array.
const [ingredients, setIngredients] = useState({});
// This will be an infinite loop, because by shallow comparison ingredients !== {}
useEffect(() => {
setIngredients({});
}, [ingredients]);
// If we need to update ingredients then we need to manually confirm
// that it is actually different by deep comparison.
useEffect(() => {
if (is(<similar_object>, ingredients) {
return;
}
setIngredients(<similar_object>);
}, [ingredients]);
The main problem is that useEffect compares the incoming value with the current value shallowly. This means that these two values compared using '===' comparison which only checks for object references and although array and object values are the same it treats them to be two different objects. I recommend you to check out my article about useEffect as a lifecycle methods.
The best way is to compare previous value with current value by using usePrevious() and _.isEqual() from Lodash.
Import isEqual and useRef. Compare your previous value with current value inside the useEffect(). If they are same do nothing else update. usePrevious(value) is a custom hook which create a ref with useRef().
Below is snippet of my code. I was facing problem of infinite loop with updating data using firebase hook
import React, { useState, useEffect, useRef } from 'react'
import 'firebase/database'
import { Redirect } from 'react-router-dom'
import { isEqual } from 'lodash'
import {
useUserStatistics
} from '../../hooks/firebase-hooks'
export function TMDPage({ match, history, location }) {
const usePrevious = value => {
const ref = useRef()
useEffect(() => {
ref.current = value
})
return ref.current
}
const userId = match.params ? match.params.id : ''
const teamId = location.state ? location.state.teamId : ''
const [userStatistics] = useUserStatistics(userId, teamId)
const previousUserStatistics = usePrevious(userStatistics)
useEffect(() => {
if (
!isEqual(userStatistics, previousUserStatistics)
) {
doSomething()
}
})
In case you DO need to compare the object and when it is updated here is a deepCompare hook for comparison. The accepted answer surely does not address that. Having an [] array is suitable if you need the effect to run only once when mounted.
Also, other voted answers only address a check for primitive types by doing obj.value or something similar to first get to the level where it is not nested. This may not be the best case for deeply nested objects.
So here is one that will work in all cases.
import { DependencyList } from "react";
const useDeepCompare = (
value: DependencyList | undefined
): DependencyList | undefined => {
const ref = useRef<DependencyList | undefined>();
if (!isEqual(ref.current, value)) {
ref.current = value;
}
return ref.current;
};
You can use the same in useEffect hook
React.useEffect(() => {
setState(state);
}, useDeepCompare([state]));
You could also destructure the object in the dependency array, meaning the state would only update when certain parts of the object updated.
For the sake of this example, let's say the ingredients contained carrots, we could pass that to the dependency, and only if carrots changed, would the state update.
You could then take this further and only update the number of carrots at certain points, thus controlling when the state would update and avoiding an infinite loop.
useEffect(() => {
setIngredients({});
}, [ingredients.carrots]);
An example of when something like this could be used is when a user logs into a website. When they log in, we could destructure the user object to extract their cookie and permission role, and update the state of the app accordingly.
my Case was special on encountering an infinite loop, the senario was like this:
I had an Object, lets say objX that comes from props and i was destructuring it in props like:
const { something: { somePropery } } = ObjX
and i used the somePropery as a dependency to my useEffect like:
useEffect(() => {
// ...
}, [somePropery])
and it caused me an infinite loop, i tried to handle this by passing the whole something as a dependency and it worked properly.
Another worked solution that I used for arrays state is:
useEffect(() => {
setIngredients(ingredients.length ? ingredients : null);
}, [ingredients]);