Setting state after data load on useEffect - reactjs

Attempting to update state after data load on useEffect. Able to update a local variable but not state. I am following an example from https://www.robinwieruch.de/react-hooks-fetch-data/ where Robin sets state in a similar way. For some reason, the state variable is never set correctly in my case.
Using AWS Amplify to load graphQL data. Seems to work successfully for local variable but not state variable.
const [serviceTypes, setServiceTypes] = useState([{}]);
let myServiceTypes = [{}]; // try it with a local variable to debug
useEffect(() => {
let unmounted = false;
async function fetchData() {
const result = await API.graphql(
graphqlOperation(queries.listServiceTypes)
);
console.log(result);
console.log('setting service types...');
console.log(result.data.listServiceTypes.items);
setServiceTypes(result.data.listServiceTypes.items);
myServiceTypes = result.data.listServiceTypes.items;
console.log(myServiceTypes); // set correctly
console.log(serviceTypes); // empty
}
if (!unmounted) {
fetchData();
}
return () => {
unmounted = true;
};
}, []);
Expecting serviceTypes to be set to the data loaded. Instead it is empty.

setState does not work synchronously like that. You cannot expect your serviceTypes variable to immediately contain the data right after you set it. It will be updated when the component re-renders. try moving the console.log(serviceTypes); outside of the useEffect.
see https://stackoverflow.com/a/36087156/5273790 for an explanation of setState async.

It's because you aren't thinking about the effect and renders correctly.
Mounting/Render 1
fetchData is called
setServiceTypes and passes the service type to the next render
serviceTypes is empty
Render 2
useEffect is not called
serviceTypes is now what the previous serviceTypes was set to
Try logging out the serviceTypes right before the render/return

Related

How do I properly set the state to a response output in React using Typescript?

I'm new to React and I'm sure this is a very simple solution. However, I can't seem to figure out how to properly set the state of a component to be the response from a REST call I make.
Here's the response object format I'm returning
export interface CoinMarketChartsAll {
oneDay: Array<CoinChartDataMapped>;
sevenDay: Array<CoinChartDataMapped>;
oneMonth: Array<CoinChartDataMapped>;
}
And here's my functional component where I fetch this data and try to set the state equal to it
const [loading, setLoading] = useState(true);
const [cryptoData, setCryptoData] = useState<CoinMarketChartsAll>();
useEffect(() => {
fetchMarketChart();
}, []);
const fetchMarketChart = async () => {
setLoading(true);
if (!cryptoData) {
getCoinMarketChart('lgcy-network').then((response) => {
setCryptoData(response);
setLoading(false);
})
.catch((error) => {
console.log(error);
setLoading(false);
});
}
}
I tried logging the output inside the if statement and cryptoData was always equal to undefined. Until I toggled the if statement to be
if (cryptoData) {
Then it would load it into the state, but when I reloaded the page it would never make it into the if statement. Can anyone please point out what I'm doing wrong? Thanks!
I tried logging the output inside the if statement and cryptoData was always equal to undefined.
That is simply because the state setter takes effect after a "tick": you can do setCryptoData(true), but cryptoData will remain undefined until that next tick.
when I reloaded the page it would never make it into the if statement
When you first changed your if condition, React Hot Module swap kicked in and updated your component, while preserving its current state, therefore your cryptoData state was already truthy.
But after page reload, you get a "fresh" component instance, which starts with a freshly initialized state, i.e. with undefined value, since you define it with no initial value (useState<CoinMarketChartsAll>(/* no initial value */))

In useEffect, how do I correctly update state first before running subsequent logics?

I ran into an issue where some logic within useEffect runs on mount once before a state update within it is triggered. Example below:
function App() {
const [account, setAccount] = useState("0x123");
useEffect(() => {
async function main() {
let fetchedAccount = await //some fetch logic to get new account
setAccount(fetchedAccount);
}
console.log(account);
let result = await someFunction(account);
}
I realized when running this, my App runs the logic with the predefined state i.e. someFunction("0x123") before running someFunction("updated state from fetchedAccount"). So console.log(account) shows "0x123" once and then once more for the fetchedAccount string.
How can I avoid this behavior and just have the useEffect run someFunction(accounts) after setAccount(fetchedAccount) is done?
Previously when it was a class component, I used this.setState to update the 'account' state and the someFunction(this.state.account) worked correctly. It doesn't run with the predefined state and only runs with the updated value after this.setState.
Thanks in advance!
Try adding another useEffect hook that includes 'account' in the dependency array.
useEffect(() => {
if(account !== "0x123"){
let result = await someFunction(account);
}
}, [account])
This should ensure that someFunction will only run if value of account changes.
edit: hmm, would adding a conditional to check that account is not "0x123" fix your issue?
Also, you could set initial state to undefined.

My code doesn't fetch data until after a re-render

So i have been trying to fetch data from an API and the link was dependent on a url parameter, but the data is fetched only after a re-render and i cant access them in my render function.
It works on normal pages but when i try to fetch data inside a route depending on a parameter passed with that route, it doesnt work. How could i solve this problem?
NOTE: my fetch is inside a different component with a different file from the one im passing the parameter with
const { name } = useParams();
const [countries, setCountries] = useState([]);
const fetchData = async () => {
const { data } = await axios.get(`https://restcountries.eu/rest/v2/name/${name}`);
setCountries(data);
};
useEffect(() => {
fetchData();
}, []);
Its normal behavior as you and sending an async call to fetch data, and at that time 1st render is called,
To handle this you should add a new state called loading and set it true before calling API and set it false once you got data,
and on bases on loading state, show some loader in ur render method
First of all, you should set dependencies array in your useEffect, so for now, you've set it as empty [], and it means that it will execute only the first render. So, as I understood, you wanna execute ur fetchData function every 'name' params update, so you need to add that param to ur useEffect function, this way:
useEffect(() => {
fetchData();
}, [name]);
Hope I've understood ur request correctly.
I was trying access the fetched data before specifying the index and I didn't use map.

How to fix multiple call fetch data in forEach() using React Hooks

In react Hooks, I am trying to fetch data from the API array but in the Foreach function, the API call causes infinity.
How to fix this?
const [symbols, setSymbols] = useState([]);
getPortfolioSymbolList(portfolio_name).then(data => data.json()).then(res => {
res.forEach((symbol_data)=>{
fetchPrice(symbol_data.symbol).then(price => {
setSymbols(price);
});
})
}
function fetchPrice(symbol){
const price = fetch(`api_url`)
.then(chart => chart.json())
return price;
}
Here, call fetchPrice() causes in infinite.
Setting the state will always cause a rerender
What happens in your code is the request is made and then the data is set causing a rerender. Then because of the rerender the request is made again and sets the state again and causes the rerender again.
If you have a request for data you probably want to put a React.useEffect so it only requests once.
React.useEffect(() => {
/* your data request and data set */
}, []); // the [] will only fire on mount.
Is is because your setSymbols call inside forEach makes component rerender (reload) - it means that all of your main component function is call again and again... getPortfolioSymbolList too. You have to use useEffect hook to resolve this problem. Your getPortfolioSymbolList() API call should be inside useEffect.
https://reactjs.org/docs/hooks-effect.html
PROBLEM
Your first symbol is updated in your API call, which triggers a re-render of the component calling the API call to go on an infinite loop.
SOLUTION
Wrap your API in your useEffect. The function inside your useEffect will only be called once. See useEffect docs here
You need to use for await of to loop asynchronously. forEach can't loop asynchronously. See for await of docs here
Update your symbols once all the data is collected.
function Symbols() {
const [symbols, setSymbols] = useState([]);
React.useEffect(() => {
async function fetchSymbols() {
const portfolio = await getPortfolioSymbolList(portfolio_name);
const jsonPortfolios = await data.json();
const symbols = [];
for await (let jsonPortfolio of jsonPortfolios) {
const price = await fetchPrice(jsonPortfolio.symbol);
symbols.push(price);
}
setSymbols(symbols);
}
fetchSymbols();
}, []);
return /** JSX **/
}

Howcome my state is not updating using react hooks and use Effect

My useEffect function is trying to fetch data from an API endpoint. The results resultAllTestTypes are currently logging fine.
However, I can't find out why the allTestTypes are coming back as undefined as I thought I had already set it in a state variable it should be able to log it to the console. But when I log the allTestTypes data it gives me this.
Code:
const [allTestTypes, setAllTestTypes] = useState([])
useEffect(() => {
async function onLoadCreateUnitTests() {
const results = await get('get_tables_autocomplete/b', user.user)
const resultsAllTestTypes = await get('get_all_test_types', user.user)
autoComplete.setTablesAutoComplete(results)
setAllTestTypes(resultsAllTestTypes)
console.log('resultAllTestTypes data ',resultsAllTestTypes.data);
console.log('allTestTypes data ',allTestTypes.data);
}
onLoadCreateUnitTests()
It's setting the state, you just have a console.log in a spot that's not particularly useful.
allTestTypes is a local const. It will never change, and that's not what setAllTestTypes is trying to do. When you set state, this tells react to render the component again. When that render occurs, you'll make a new call to useState, which will return the new value and assign it to a new local const. That new variable can be interacted with by code in the new render, but code from the previous render (such as your console.log) will never see the new value.
If you'd like to verify that the component is rerendering with a new value, move your console.log into the body of the component:
const [allTestTypes, setAllTestTypes] = useState([])
console.log('Rendering with', allTestTypes);
useEffect(() => {
async function onLoadCreateUnitTests() {
const results = await get('get_tables_autocomplete/b', user.user)
const resultsAllTestTypes = await get('get_all_test_types', user.user)
autoComplete.setTablesAutoComplete(results)
setAllTestTypes(resultsAllTestTypes)
}
onLoadCreateUnitTests()
});
cuz setAllTestTypes is async, so u can't get it immediately.
if u want to use it ,use the local variable resultsAllTestTypes instead

Resources