React infinity loop when making HTTP calls using useEffect - reactjs

I am trying to make 2 HTTTP calls inside a React component that will then call the setters for 2 properties that are defined using useState. I have followed what I thought was the correct way of doing so in order to prevent inifinite rerendering but this is still happening. Here is my code:
function Dashboard({ history = [] }) {
const [teamInfo, setTeamInfo] = useState(null);
const [survey, setSurvey] = useState(null);
const [open, setOpen] = useState(false);
const user = getUser();
const getSurveyHandler = async () => {
const surveyResponse = await getSurveys('standard');
setSurvey(surveyResponse.data);
};
const getTeamInfoHandler = async () => {
const teamInfoResponse = await getTeamInfo(user.teamId);
setTeamInfo(teamInfoResponse);
};
useEffect(() => {
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, [survey, teamInfo]);
As you can see, I have defined the functions outside of the useEffect and passed in the two state variables into the dependency array that will be checked to prevent infinite rerendering.
Can anyone see why this is still happening?
Thanks

You are setting survey and teamInfo in your functions with a dependency on them in your useEffect.
useEffect runs everytime a dependency changes. You are setting them, causing a rerender. Since they changed, the useEffect runs again, setting them again. The cycle continues.
You need to remove those.
useEffect(() => {
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, []);
The only other thing recommended is to move async functions inside the useEffect unless you need to call them from other places in the component.
useEffect(() => {
const getSurveyHandler = async () => {
const surveyResponse = await getSurveys('standard');
setSurvey(surveyResponse.data);
};
const getTeamInfoHandler = async () => {
const teamInfoResponse = await getTeamInfo(user.teamId);
setTeamInfo(teamInfoResponse);
};
document.body.style.backgroundColor = '#f9fafb';
getSurveyHandler();
getTeamInfoHandler();
}, []);

Related

ReactJS - use localStorage as a dependency for useEffect causes infinite loop

This code give me infinite loop at line console.log
const userInfo = JSON.parse(localStorage.getItem("user_info"));
const [filterSemester, setFilterSemester] = useState(SEMESTERS[0]);
const [scoreData, setScoreData] = useState(null);
useEffect(() => {
getData();
}, [userInfo, filterSemester]);
useEffect(() => {
console.log("scoreData: ", scoreData);
}, [scoreData]);
const getData = () => {
const params = {
student_id: userInfo?.student_info?.id,
school_year_id:
userInfo?.student_info?.class_info?.grade_info?.school_year_id,
semester: filterSemester.key,
};
getStudyInfoBySchoolYear(params).then((res) => {
if (res?.status === 200) {
setScoreData(res?.data?.data);
}
});
};
If I remove userInfo from the dependency array of the first useEffect, the loop will gone, I wonder why? I didn't change it at all in the code.
userInfo is actually changing.
It is a functional component, so all the code that is inside the component will run on every render, thus, userInfo gets re-created on every render, because it was not declared as a reference (with useRef) or, more commonly, as a state (with useState).
The flow is as follows:
The component mounts.
The first useEffect runs getData. The second useEffect also runs.
getData will update scoreData state with setScoreData. This latter will trigger a re-render, and also scoreData has changed, so the second useEffect will run.
When the render takes place, all the code within your component will run, including the userInfo declaration (creating a new reference to it, unless localStorage.getItem("user_info") is returning undefined).
React detects userInfo as changed, so the first useEffect will run again.
The process repeats from step 3.
You could replace your
const userInfo = JSON.parse(localStorage.getItem("user_info"));
with
const userInfo = React.useRef(JSON.parse(localStorage.getItem("user_info")));
and your
useEffect(() => {
getData();
}, [userInfo, filterSemester]);
with
useEffect(() => {
getData();
}, [userInfo.current, filterSemester]);
try this
const userInfo = JSON.parse(localStorage.getItem("user_info"));
const [filterSemester, setFilterSemester] = useState(SEMESTERS[0]);
const [scoreData, setScoreData] = useState(null);
useEffect(() => {
getData();
}, [localStorage.getItem("user_info"), filterSemester]);
useEffect(() => {
console.log("scoreData: ", scoreData);
}, [scoreData]);
const getData = () => {
const params = {
student_id: userInfo?.student_info?.id,
school_year_id:
userInfo?.student_info?.class_info?.grade_info?.school_year_id,
semester: filterSemester.key,
};
getStudyInfoBySchoolYear(params).then((res) => {
if (res?.status === 200) {
setScoreData(res?.data?.data);
}
});
};

Custom React Hooks and what to keep inside useEffect hook

I have the following code and I wonder how I can improve performance, specifically, should I move the const fuse = new Fuse... section and the buildSearchRequest function within useEffect so it is called only when the search query is changed? I have noticed my code that consumes the custom hooks hits the new Fuse section many times.
const [searchResults, setSearchResults] = React.useState([])
const fuse = new Fuse(DummySearchResponse.results, {
keys: ["data.programmeTitle"],
includeScore: true,
threshold: 0.2,
})
const searchApiUrlStart = "http://mimir.prd.oasvc.itv.com/search?query="
const searchApiUrlEnd =
"&entityType=programme&streamingPlatform=itv_hub&checkAvailability=true"
const buildSearchRequest = (searchString) => {
return (
searchApiUrlStart +
encodeURIComponent(searchString) +
searchApiUrlEnd
)
}
React.useEffect(() => {
if (!query) return
const fetchData = async () => {
let searchData
if (useLiveSearchApi) {
const liveResponse = await fetch(
"http://mimir.prd.oasvc.itv.com/search?query=" +
buildSearchRequest(query) +
"&entityType=programme&streamingPlatform=itv_hub&checkAvailability=true"
)
const liveJson = await liveResponse.json()
const liveResults = await liveJson.results
searchData = liveResults
} else {
const fuseResponse = await fuse.search(query)
const fuseJson = await fuseResponse.map((result) => {
return result.item
})
searchData = fuseJson
}
const mappedResults = await searchData.map((searchItem) => ({
title: searchItem.data.programmeTitle,
contentImageUrl: searchItem.data.imageHref,
programmeCCId: searchItem.data.programmeCCId,
episodeId: searchItem.data.episodeId,
}))
setSearchResults(mappedResults)
}
fetchData()
}, [query])
return { searchResults }
}```
First and foremost, avoid declaring a callback within the useEffect. What you need to do is use the useCallBack hook to declare your fetchData callBack
Your code should atleast look like...
const fetchData = useCallBack(() => {
// Your Fetch Data Code
}, [<dependency array>]) // Here, you want to add all dependencies whose current state you need. Note that if a dependency is not added here, and you use it within the useCallBack, you'll only access a stale state (state during initialization), and never the updated dependency state.
// Here are three ways you can declare your useEffect
useEffect(fetchData, [query]); You probably wanna use this one. Less lines and much cleaner.
useEffect(() => fetchData(), [query]);
useEffect(() => {
fetchData();
}, [query]);
Your useEffect will only be called once the query variable has been updated. So, to ensure a sideEffect is not triggered, ensure your useEffect or the useCallBack specified as the trigger does not update the variable. If this is the case, your code will be stuck in an indefinite loop.

Why my custom hook causes infinite data refetching?

My component gets the hashed-id from the query string, then calls api with that hash to fetch a post for review.
eslint forces me to add my custom hook to dependency array.
fetchpost();
}, [query]);
But doing this causes an infinite loop. In order to stop it I need to disable this eslint rule, as seen below.
// component file
const history = useHistory();
const dispatch = useDispatch();
const query = useQuery();
const [post, setPost] = useState(null);
const [hash, setHash] = useState(null);
useEffect(() => {
const fetchpost = async () => {
const hash = query.get("hashed_id");
const post = await fetchReviewPost(
`/api/posts/${hash}/review`
);
setHash(hash);
setPost(post);
};
fetchpost();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// utils file
import { useLocation } from "react-router-dom";
export const getCurrentURL = () => {
return document.URL;
};
export const useQuery = () => {
const queryString = useLocation().search;
return new URLSearchParams(queryString);
};
Dan Abramov writes An infinite loop may also happen if you specify a value that always changes in the dependency array.
Is that the case here? Is query reference different on every render? And why eslint wants to put it in a dependency array?
He also says removing a dependency you use (or blindly specifying []) is usually the wrong fix. Which I sort of did by disabling the eslint rule.
Any thoughts?
If you really want to keep sticking to eslint suggestions and using the useQuery hook, here is an alternative way:
// component file
const history = useHistory();
const dispatch = useDispatch();
const q = useQuery();
const [query] = useState(q);
const [post, setPost] = useState(null);
const [hash, setHash] = useState(null);
useEffect(() => {
const fetchpost = async () => {
const hash = query.get("hashed_id");
const post = await fetchReviewPost(
`/api/posts/${hash}/review`
);
setHash(hash);
setPost(post);
};
fetchpost();
}, [query]);
At this point the query value keeps constant across the subsequent function calls.
However, I'd remove the useQuery hook, and place its content straight into the fetchpost function.

Infinite loop in useEffect when setting state

I've got a question about useEffect and useState inside of it.
I am building a component:
const [id, setId] = useState(0);
const [currencies, setCurrencies] = useState([]);
...
useEffect(()=> {
const getCurrentCurrency = async () => {
const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
const data = await response.json();
const currencyArray = [];
const {EUR:euro ,CHF:franc, USD: dolar} = data.rates;
currencyArray.push(euro, dolar/franc,1/dolar);
console.log("currencyArray", currencyArray);
setCurrencies(currencies => [...currencies, currencyArray]);
}
getCurrentCurrency();
}, [id, currencies.length]);
Which is used for making a new API request when only id change. I need to every time that ID change make a new request with new data. In my case now I have infinite loop. I try to use dependencies but it doesn't work as I expected.
You changing a value (currencies.length), which the useEffect depends on ([id, currencies.length]), on every call.
Therefore you cause an infinite loop.
useEffect(() => {
const getCurrentCurrency = async () => {
// ...
currencyArray.push(euro, dolar / franc, 1 / dolar);
// v The length is changed on every call
setCurrencies(currencies => [...currencies, currencyArray]);
};
getCurrentCurrency();
// v Will called on every length change
}, [id,currencies.length]);
You don't need currencies.length as a dependency when you using a functional useState, currencies => [...currencies, currencyArray]
useEffect(() => {
const getCurrentCurrency = async () => {
...
}
getCurrentCurrency();
}, [id]);
Moreover, as it seems an exchange application, you might one to use an interval for fetching the currency:
useEffect(() => {
const interval = setInterval(getCurrency, 5000);
return () => clearInterval(interval);
}, []);
you can just call the useEffect cb one the component mounted:
useEffect(()=>{
//your code
// no need for checking for the updates it they are inside the component
}, []);

React useEffect but after set state value and only once

I'm trying to migrate some of my old componentDidMount code to the new useEffect hooks and I'm having problems figuring out how to emulate the callback behavior of setState
I have an array of stuff that gets pulled from an api, I need to call a function only after the state and been loaded and then only once
Previous code:
ComponentDidMount() {
const response = await getMyArrayFromAPI
this.setState({ myArray }, () => { initializeArray() })
}
Current code:
const [myArray, setMyArray] = useState([])
useEffect(() = {
const response = await getMyArrayFromAPI
setMyArray(response)
}, [])
useEffect(() => {
// one time initialization of data
// initially gets called before myArray has value, when it should be after
// gets called every time myArray changes, instead of only once
}, [myArray])
you can set myArray in the first useEffect function, but if you want to use separate functions you can just check if it's empty
useEffect(() => {
if (!myArray.length) {
// one time initialization
}
}, [myArray])
You can use the state to drive whether or not initializeArray needs to run e.g.
const [array, setArray] = useState(null);
useEffect(() => {
getMyArrayFromAPI.then(data => setArray(data || []));
}, []);
if (array) {
// this will only ever run once as we don't set `array`
// anywhere other than `useEffect`
initializeArray();
}
Depending on what initializeArray actually does, you could run it from inside then but that's entirely up to you.
I guess you could create a custom setState hook to manage your callback
const useMyCustomStateHook = (initState, cb) => {
const [customState, updateCustomState] = useState(initState);
useEffect(() => cb(customState), [customState, cb]);
return [customState, updateCustomState];
};
So you could then have
import React, {useState,useEffect} = from 'react'
const [myArray, setMyArray] = useMyCustomStateHook([], initializeArray)
useEffect(() = {
const response = await getMyArrayFromAPI
setMyArray(response)
}, [])

Resources