just starting to learn hooks here.
I am getting data from firestore and trying to set it to state using hooks. when I uncomment the line doing so, I get stuck in an infinite loop. no error, but the console goes crazy with logging the state thousands of times.
Let me know if you need more info!
function Lists(props) {
const [lists, setLists] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const subscriber =
firestore().collection('users').doc(props.user).collection('lists')
.onSnapshot(QuerySnapshot => {
const items = []
QuerySnapshot.forEach(documentSnapshot => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
//setLists(items)
setLoading(false)
console.log(lists)
})
})
// unsubscribe from firestore
return () => subscriber();
})
//rest of func..
this issue happens becauase useEffect gets called over and over again. useEffect is like componentDidMount and componentDidUpdate if you are familiar with React class components.
so whenever you set the state inside the useEffect, you trigger an update, and then, useEffect gets called again, and thus the infinite loop.
to fix this, useEffect accepts a extra argument, which is an array of dependancies, which indicates that this useEffect call should only re-executed whenever a change happens to one of its dependancies. in your case you can provide an empty array, telling react that this useEffect should only be called one time.
useEffect(() => {
const subscriber =
firestore().collection('users').doc(props.user).collection('lists')
.onSnapshot(QuerySnapshot => {
const items = []
QuerySnapshot.forEach(documentSnapshot => {
items.push({
...documentSnapshot.data(),
key: documentSnapshot.id,
});
//setLists(items)
setLoading(false)
console.log(lists)
})
})
// unsubscribe from firestore
return () => subscriber();
}, []) // <------------ the second argument we talked about
Related
Hi I am learning React Hooks I know useEffect when used without any dependency array should run after every render but what about render itself does it happen every time a state changes because I am trying following code where I am rending a static h1 to the DOM and in my useEffect I am using a fetch to call a json file on the success of the fetch I am setting a state loaded to true from false I have written this example for the learning purpose only I was expecting that this should have caused a infinite loop because useEffect without dependency array should run after every render and I am calling set state which according to me should force the render to happen but It only run the useEffect twice but
when I am using the other example where I am using useState create a name state and in the useeffect without dependency arry I am changing the name state with Math.random it is behaving as expected causing infinite loop what I understand is useState does not cause render state to happen untill the value of the state changed as in the first example the state value only changed once from false to true and in name example it is random so changes every time
Just need a to know is my understanding correct and also if you can share where I can learn these Kind of stuff as I did not find this on the react doc.
example 1 with fetch
`
const App = () => {
// const [name, setName] = useState('sumit');
const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
})
// setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
example 2 with name
const App = () => {
const [name, setName] = useState('sumit');
// const [loaded, setLoaded] =useState(false);
useEffect(() => {
console.log('sumit');
/* fetch('./option.json').then((res) => {
console.log(res);
return res.json();
}).then((res) => {
console.log(res);
setLoaded(true);
}) */
// setcount(count+1);
setName(Math.random());
})
return (
<h1>sumit</h1>
);
}
`
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 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.
I'm beginner with React. I have 2 different cases where I'm using React Hooks which I cannot receive the data from my local API properly.
Case 1:
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test)
})
...
}
It works with the state test displaying correctly the content from the API but I don't know why/how the Axios continues calling the API infinity - endless. (Ps: the very first call it returns undefined, then the next ones it works) What am I doing wrong?
To fix this I've tried to use useEffect like this (Case 2):
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data)
console.log(test);
})
}, [])
...
}
Now the Axios works only once but no data is coming from the API. Maybe I should use async/wait for this case but I cannot make it work. Does anyone know how to fix that (Case 1 or/and Case 2)?
Thanks.
Updating the state is an asynchronous operation. So the state is not really updated until the next time the component gets rendered. If you want to capture the correct state, you can either console.log(res.data) or wrap that inside the useEffect hook with test as dependency.
export const RegisterT = () => {
const [test, setTest] = useState()
const addrState = {}
// effect only runs when component is mounted
useEffect(() => {
axios.get('http://127.0.0.1:3333/states', { addrState })
.then(res => {
setTest(res.data);
});
}, []);
// effect runs whenever value of test changes
useEffect(() => {
console.log(test);
}, [test]);
}
That way it is guaranteed that the console.log runs when the value of test is updated.
Also the reason the API request is invoked once is you have not mentioned anything in the dependency array. [] empty dependency array runs the effect when the component is mounted for the first time.
async/await is just a wrapper around Promise object. So they would behave similarly.
The solution with useEffect is good. If you don't use it each render will call the request. This is the same if you put there console.log with any information. The reason why you don't see the data in the useEffect is that the value of the state is not updated in current render but in the next which is called by setter of the state. Move the console.log(test); after useEffect to see the data. On init it will be undefined but in the next render, it should contain the data from the request.
I'm using a componentDidUpdate function
componentDidUpdate(prevProps){
if(prevProps.value !== this.props.users){
ipcRenderer.send('userList:store',this.props.users);
}
to this
const users = useSelector(state => state.reddit.users)
useEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
but it I get the message 'users changed' when I start the app. But the user state HAS NOT changed at all
Yep, that's how useEffect works. It runs after every render by default. If you supply an array as a second parameter, it will run on the first render, but then skip subsequent renders if the specified values have not changed. There is no built in way to skip the first render, since that's a pretty rare case.
If you need the code to have no effect on the very first render, you're going to need to do some extra work. You can use useRef to create a mutable variable, and change it to indicate once the first render is complete. For example:
const isFirstRender = useRef(true);
const users = useSelector(state => state.reddit.users);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
console.log('users changed')
console.log({users})
}
}, [users]);
If you find yourself doing this a lot, you could create a custom hook so you can reuse it easier. Something like this:
const useUpdateEffect = (callback, dependencies) => {
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
} else {
return callback();
}
}, dependencies);
}
// to be used like:
const users = useSelector(state => state.reddit.users);
useUpdateEffect(() => {
console.log('users changed')
console.log({users})
}, [users]);
If you’re familiar with React class lifecycle methods, you can think
of useEffect Hook as componentDidMount, componentDidUpdate, and
componentWillUnmount combined.
As from: Using the Effect Hook
This, it will be invoked as the component is painted in your DOM, which is likely to be closer to componentDidMount.