useEffect dependency cause an infinite loop - reactjs

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

Related

My custom React hook method "useFetch" is running 8 times when called

Hope anyone is able to help me with a custom react hook.
My custom react hook "useFetch" is running 8 times when called.
Can anyone see, why it is running 8 times when the custom "useFetch" hook is called?
I am a bit new to React, but it seems like I am using useEffect method wrong. Or maybe I need to use another method.
UseFetch hook method:
import React, { useState, useEffect } from "react";
export const useFetch = function (
options = {
IsPending: true,
},
data = {}
) {
// load data
const [loadData, setLoadData] = useState(null);
// pending
const [isPending, setIsPending] = useState(false);
// error
const [isError, setIsError] = useState(false);
useEffect(() => {
// method
const fetchData = async function () {
// try
try {
// set pending
setIsPending(true);
// response
const response = await fetch(data.url, data);
// handle errors
if (response.status !== 200 && response.status !== 201) {
// throw new error with returned error messages
throw new Error(`Unable to fetch. ${response.statusText}`);
}
// convert to json
const json = await response.json();
// set load data
setLoadData(json);
// set error
setIsError(false);
// set pending
setIsPending(false);
// catch errors
} catch (err) {
// set error
setIsError(`Error fetching data: ${err.message}`);
// set pending
setIsPending(false);
}
};
// invoke fetch data method
fetchData();
}, []);
// return
return {
loadData,
isPending,
isError,
};
};
export default useFetch;
Everytime you change a state in a hook, the component that has the hook in it will rerender, making it call the function again.
So let's start counting the renders/rerenders by the change of state:
Component mounted
setIsPending(true)
setLoadData(json)
setIsPending(false)
(depending if it's successful or not you might get more state changes, and therefore rerenders, and therefore hook being called again)
So 4 is not 8, so why are you getting 8?
I presume you are using React18, and React18 on development and StrictMode will call your useEffect hooks twice on mount: React Hooks: useEffect() is called twice even if an empty array is used as an argument
What can you do to avoid this?
First of all, check on the network tab how many times you are actually fetching the data, I presume is not more than 2.
But even so you probably don't want to fetch the data 2 times, even though this behaviour won't be on production and will only be on development. For this we can use the useEffect cleanup function + a ref.
const hasDataFetched = useRef(false);
useEffect(() => {
// check if data has been fetched
if (!hasDataFetched.current) {
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}
// cleanup function
return () => {
// set has data fetched to true
hasDataFetched.current = true;
};
}, []);
Or as you suggested, we can also add data to the dependency array. Adding a variable to a dependency array means the useEffect will only be triggered again, when the value of the variable inside the dependency array has changed.
(Noting that data is the argument you pass to the useFetch hook and not the actual data you get from the fetch, maybe think about renaming this property to something more clear).
useEffect(() => {
// check if data has been fetched
const fetchData = async function () {
// fetch data logic in here
};
fetchData();
}, [data]);
This will make it so, that only if loadData has not been fetched, then it will fetch it. This will make it so that you only have 4 rerenders and 1 fetch.
(There is a good guide on useEffect on the React18 Docs: https://beta.reactjs.org/learn/synchronizing-with-effects)
Every time you change the state within the hook, the parent component that calls the hooks will re-render, which will cause the hook to run again. Now, the empty array in your useEffect dependency should be preventing the logic of the hook from getting called again, but the hook itself will run.

Updating state inside useCallback - React JS

How can I restructure this code to allow a state update inside the useCallback function?
Here's what is executed first:
useEffect(() => {
getData();
}, [getData]); // errors if getData is left (missing dependency error)
In the getData function, I pass a state variable (lastDoc) to getSomething() as a parameter. It stores the last document/database row for pagination.
const [lastDoc, setLastDoc] = useState(null);
const getData = useCallback(async() => {
const data = await getSomething(lastDoc);
setLastDoc(data.lastDoc); // useSate function
}, [getSomething, lastDoc]);
This, at the moment, just causes an infinite loop where the getData function is re-rendered once setLastDoc updates the lastDoc variable, as getData has lastDoc as a dependency. If I remove the lastDoc dependency, I get the missing dependency error, which I understand to be an important error to listen to.
I think a null-check might be sufficient.
useEffect(() => {
if(lastDoc === null){
getData();
}
}, [getData, lastDoc]);

useEffect causing infinite loop or getting errors

I am trying to learn React hooks. I'm creating a simple news app that leverages the NY times api.
When I leave the dependency empty, nothing loads and when I use data as the dependency it goes into an infinite loop.
When I use isLoading, it works but then I receive an error "localhost/:1 Unchecked runtime.lastError: The message port closed before a response was received." and "localhost/:1 Error handling response: TypeError: Cannot read property 'level' of undefined"
main.js
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
console.log(data.results);
};
useEffect(() => {
fetchData();
}, [isLoading]);
return <div className="main">work</div>;
};
export default Main;
I am also receiving a warning, when using isLoading, in the terminal saying "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"
What am I doing wrong?
The infinite loop is caused by the combination of using setData(result) and [data]:
The component mounts and the useEffect is run.
setData(result) will asynchronously update the data value and trigger a rerender.
During the rerender the useEffect will be run again as data will not successfully complete the reference comparison.
Repeat 2 to 3.
The warning "React Hook useEffect has a missing dependency" is self explanatory to an extent.
Making use of an external (to the useEffect) variable that is not included in the dependency array may mean that the value of the variable changes and the useEffect will not be retriggered or that the value may not be the expected value.
Below is a an example of how the original snippet might be fixed:
import React, { useEffect, useState } from "react";
import { nyTimesApi } from "../services/Api";
const Main = () => {
const [isLoading, setIsLoading] = useState(true);
const [data, setData] = useState([]);
useEffect(() => {
// Create function inside useEffect so that the function is only
// created everytime the useEffect runs and not every render.
const fetchData = async () => {
const result = await nyTimesApi();
setData(result);
setIsLoading(false);
// setData will update state asynchronously.
// Log the value stored instead.
console.log(result.results);
};
//Run data fetching function.
fetchData();
},
// Both of these are set functions created by useState and will
// not change for the life of the component, but adding them to
// the dependency array will make your linter happy.
// Do not need to check isLoading as it is always true on component
// mount.
[setData, setIsLoading]);
return <div className="main">work</div>;
};
export default Main;
The second argument to useEffect is an array of variables, which trigger the function within useEffect to be called every time they change.
You have [isLoading] as the second argument to useEffect and update the value of this within fetchData(). This is going to cause the useEffect trigger to happen again and again and again.
If you only want to have useEffect call once (in a similar way to ComponentDidMount in class-based components), then you need to specify an empty array as the second argument.
useEffect(() => {
fetchData();
}, []);

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, ...

React Native + Firestore infinite loop, using hooks

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

Resources