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
}, []);
Related
I get data from backend and set to my state in componentdidmount but value not set after log state
const [tasks, setTasks] = useState([]);
const getTasks = async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
setTasks(response.data.data);
}
};
useEffect(() => {
getTasks();
console.log(tasks);
}, []);
My tasks is empty when i log it
So the title and the question itself are actually two questions.
React Hook useEffect has a missing dependency: 'tasks'. Either includes it or remove the dependency array
That's because you include a state (i.e. tasks) in the useEffect hook. And React is basically asking you, "Do you mean run console.log(tasks) every time tasks is updated?". Because what you are doing is run the useEffect hook once and only once.
And for your "actual" question
value not set after log state
In short, states are set in async manner in React. That means tasks is not necessary immediately updated right after you call setTasks. See #JBallin comment for details.
const [tasks, setTasks] = useState([]);
useEffect(() => {
setTimeout(async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
setTasks(response.data.data);
}
}, 1000);
console.log(tasks);
}, []);
The main problem is that useEffect -> is a sync method, getTasks() is asynchronous, and useEffect only works once when your component mounts. Shortly speaking, you got your data from the backend after useEffect worked.
For example, if you will add one more useEffect
useEffect(() => {
console.log(tasks);
}, [tasks]);
You will see log, after your data will have changed.
You can use self-calling async function inside useEffect as shown here:
const [tasks, setTasks] = useState([]);
const getTasks = async () => {
const getTodoInformation = {
email: localStorage.getItem("tokenEmail"),
};
if (getTodoInformation.email) {
const response = await axios.post(
"http://localhost:9000/api/todo/get",
getTodoInformation
);
return response.data.data;
}
};
useEffect(() => {
(async () => {
const tasks = await getTasks();
setTasks(tasks);
})();
console.log(tasks);
}, [tasks]);
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);
}
});
};
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.
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();
}, []);
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)
}, [])