React useEffect dependency array and implementation - reactjs

I'm new to react hooks and have run into a situation which I solved, but not sure if this is a proper implementation of useEffect. I have a form with some fields (answer, question etc) with some validation, but without the implementation of useEffect below, my validation was one step behind due to the async nature of setting state. After adding useEffect and the state items to the useEffect dependency array that was fixed. But the side effect of adding items to that array was re-rendering, and thus fetchData running each time the state changed. Each time fetch data finished it wiped out the changed state of any items I was changing in the form.
My solution was a "mounted" state variable which is set to true once the fetch occurs. Then if mounted was true, I don't fetch again. This solution seems to have fixed the re-fetching issue as well as the state being one step behind. Is this a proper pattern to use or is there a better/more preferred way?
const [mounted, setMounted] = useState(false)
useEffect(() => {// reactive state
// if params.id that means we need to edit an existing faq
if(params.id && !mounted){
async function fetchData() {
await fetchFaqs();
setMounted(true);
}
fetchData();
}
checkIfFormIsValid();
}, [answer, question, section, sort, checkIfFormIsValid]);

You could just use separate useEffects like this:
// add params.id to dependency array
useEffect(() => {
if (params.id) {
async function fetchData() {
await fetchFaqs();
}
fetchData();
}
}, [params.id])
useEffect(() => {
checkIfFormIsValid();
}, [answer, question, section, sort, checkIfFormIsValid])

Related

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 loop on componentdidupdate with useEffect

I'm using redux and trying to fetch data when my component did update.
I'm using useEffect hook to track posts update and getting the state with useSelector.
I'm having issues as the component is making infinite fetching requests instead of a single request.
Anyone knows how I can stop it from making infinite requests
and make a single request if posts updated?
my code:
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, [posts]);
image showing infinite fetching requests being made
From useEffect documentation
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
So, dispatch(getPosts(page)) will be called on component mount as well when any of the dependency provided get changed, this will make an API request and fetch the posts of this page. Which will eventually update the state.posts.posts once the API is successful. As, the same state.posts.posts is given as dependency to the useEffect hook this will trigger the function to get executed again and the cycle goes on.
For example if you want to make the API call and fetch new posts when there's a change in the page you should provide page as dependency instead of posts as shown below
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, [page]);
const posts = useSelector((state) => state.posts.posts);
useEffect(() => {
dispatch(getPosts(page));
}, []);
const updateNeeded = useSelector((state) => state.posts.updateNeeded);
useEffect(() => {
if (updateNeeded) {
dispatch(getPosts(page));
}
}, [updateNeeded]);
Change updateNeeded to true by a dispatch action when you want to fetch a new update, and when the update is fetched dispatch an action which will make this flag to false again.

Infinite Loop with useEffect - ReactJS

I have a problem when using the useEffect hook, it is generating an infinite loop.
I have a list that is loaded as soon as the page is assembled and should also be updated when a new record is found in "developers" state.
See the code:
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = async () => {
await api.get('/developers').then(response => {
setDevelopers(response.data);
});
};
// This way, the loop does not happen
useEffect(() => {
getDevelopers();
}, []);
// This way, infinte loop
useEffect(() => {
getDevelopers();
}, [developers]);
console.log(developers)
If I remove the developer dependency on the second parameter of useEffect, the loop does not happen, however, the list is not updated when a new record is found. If I insert "developers" in the second parameter of useEffect, the list is updated automatically, however, it goes into an infinite loop.
What am I doing wrong?
complete code (with component): https://gist.github.com/fredarend/c571d2b2fd88c734997a757bac6ab766
Print:
The dependencies for useEffect use reference equality, not deep equality. (If you need deep equality comparison for some reason, take a look at use-deep-compare-effect.)
The API call always returns a new array object, so its reference/identity is not the same as it was earlier, triggering useEffect to fire the effect again, etc.
Given that nothing else ever calls setDevelopers, i.e. there's no way for developers to change unless it was from the API call triggered by the effect, there's really no actual need to have developers as a dependency to useEffect; you can just have an empty array as deps: useEffect(() => ..., []). The effect will only be called exactly once.
EDIT: Following the comment clarification,
I register a developer in the form on the left [...] I would like the list to be updated as soon as a new dev is registered.
This is one way to do things:
The idea here is that developers is only ever automatically loaded on component mount. When the user adds a new developer via the AddDeveloperForm, we opportunistically update the local developers state while we're posting the new developer to the backend. Whether or not posting fails, we reload the list from the backend to ensure we have the freshest real state.
const DevList: React.FC = () => {
const [developers, setDevelopers] = useState<DevelopersData[]>([]);
const getDevelopers = useCallback(async () => {
await api.get("/developers").then((response) => {
setDevelopers(response.data);
});
}, [setDevelopers]);
useEffect(() => {
getDevelopers();
}, [getDevelopers]);
const onAddDeveloper = useCallback(
async (newDeveloper) => {
const newDevelopers = developers.concat([newDeveloper]);
setDevelopers(newDevelopers);
try {
await postNewDeveloperToAPI(newDeveloper); // TODO: Implement me
} catch (e) {
alert("Oops, failed posting developer information...");
}
getDevelopers();
},
[developers],
);
return (
<>
<AddDeveloperForm onAddDeveloper={onAddDeveloper} />
<DeveloperList developers={developers} />
</>
);
};
The problem is that your getDevelopers function, calls your setDevelopers function, which updates your developers variable. When your developers variable is updated, it triggers the useEffect function
useEffect(() => {
getDevelopers();
}, [developers]);
because developers is one of the dependencies passed to it and the process starts over.
Every time a variable within the array, which is passed as the second argument to useEffect, gets updated, the useEffect function gets triggered
Use an empty array [] in the second parameter of the useEffect.
This causes the code inside to run only on mount of the parent component.
useEffect(() => {
getDevelopers();
}, []);

React Hooks Firebase - useEffect hook not returning any data

I am trying to use the useEffect hook in React to listen for changes in a location in firestore.
Initially I didn't have an empty array as the second prop in the useEffect method and I didn't unsubscribe from the onSnapshot listener. I received the correct data in the projects variable after a short delay.
However, when I experienced extreme performance issues, I added in the unsubscribe and empty array which I should have put in earlier. Strangely, now no data is returned but the performance issues are gone.
What might be preventing the variable updating to reflect the data in firestore?
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, []);
return projects
};
const projects = useProjects(organisation);
You'll need a value in the dependency array for the useEffect hook. I'd probably suggest the values you are using in the useEffectHook. Otherwise with [] as the dependency array, the effect will only trigger once (on mount) and never again. The point of the dependency array is to tell react to re run the hook whenever a dependency changes.
Here's an example I'd suggest based on what's in the hook currently (using the id that you send to firebase in the call). I'm using optional chaining here as it makes the logic less verbose.
function useProjects(organisation) {
const [projects, setProjects] = useState({
docs: []
});
useEffect(() => {
if (!organisation.docs[0]) return;
const unsubscribe = firebase.firestore().collection('organisations').doc(organisation.docs[0].id).collection("projects").onSnapshot(snapshot => {
setProjects(snapshot);
});
return () => unsubscribe()
}, [organization.docs[0]?.id]);
return projects
};

Listener does not trigger useEffect with dependency array

The goal here is to change table data when data is modified in Firebase
The issue is that for some reason, within the scope of firebase listener, setState(...) does not trigger the useEffect(()=> {}, [...]) with the appropriate dependencies.
Any idea why, and is there a way to force the change?
const [actions, setActions] = useState([]);
....
firebase.
.firestore()
.collection("collection_name")
.onSnapshot(s => {
if (!s.empty) {
s.docChanges().forEach(change => {
if (change.type === "modified") {
const newActions = [...actions, change.doc.data()]
setActions(newActions) //The change
console.log("arrived here")
}...
...
useEffect(() => {
console.log("change in actions"); // Does not triggered on when modified
}, [actions]);
This does work without the dependency array:
useEffect(() => {
console.log("This does work")
});
I think you should look other your references.
The thing with react hook is that if the reference of the object before and after the setter is the same, the useEffect listening to this object wont trigger.
Meaning that if in newActions = ... you use the reference leading to actions to update the value of action and then setActions() the reference of the object stay the same, instead you should try to make a copy of actions and then modify this independent copy. You can then setActions(modifiedCopy) and this will normally trigger the useEffect.
note: that's only a guess cause i can't know what you putted behind newActions = ...

Resources