setData in UseEffect not populating Data - reactjs

My useEffect populates tempStocksData, which is passed into setStockData() when the Promise is fulfilled. As shown in the code below, print out tempStocksData and stockData, which is supposed to be populated since I called setStockData(tempStocksData). You can see that Promise is fulfilled since it executes the prints. However, stockData is empty. For some reason setStockData is not working since stockData is not being populated. Below is the code for reference:
const [ stockData, setStockData ] = useState([])
const getStocksData = (stock) => {
return axios.get(`${BASE_URL}?symbol=${stock}&token=${TOKEN}`).catch((error) => {
console.error("Error", error.message)
})
}
useEffect(()=> {
let tempStocksData = []
const stocksList = ["AAPL", "MSFT", "TSLA", "FB", "BABA", "UBER", "DIS", "SBUX"];
let promises = [];
stocksList.map((stock) => {
promises.push(
getStocksData(stock)
.then((res) =>
{tempStocksData.push({
name: stock,
...res.data
})
})
)
})
Promise.all(promises).then(()=>{
console.log(tempStocksData)
setStockData(tempStocksData)
console.log(stockData)
})
}, [])
Please help me resolve this issue. Let me know if there is something I'm missing or something that I'm doing that is not up to date with versions/dependencies or if I'm doing Promise() js wrong.

Are you even entering your Promise.all sequence to begin with?
You are already ending the promise by having a .then function after getting the stockdata.
stocksList.map((stock) => {
promises.push(
getStocksData(stock)
)
})
Promise.all(promises).then((result)=>{
const tempStocks = result.map((stock) => {
return {
name: stock.name,
data: stock.data
}
});
console.log(tempStocksData)
setStockData(tempStocksData)
console.log(stockData)
})
Note: Above code is untested but is made to show the point

Try using the spread operator when you setStockData
like this
setStockData([...tempStocksData])

Since I've stumbled across this issue today while looking up a setData issue, let me clarify some things.
Others have pointed out that your use of promises is probably not what you actually intend to do.
Regardless, it is important to understand that a console.log of stockData inside the same useEffect that issues setStockData (even when the setter is called "before" the logging attempt) will not show the updated data in the console.
This is because all setters from useState are batched together inside useEffect calls and the corresponding getter (stockData in this case) will only reflect the updated value in the next rendering loop. It will, however, be made available when rendering or to any other hooks listening to changes to stockData.
You can find an example implementation on StackBlitz. Note that the console.log will show an empty array even though the view is updated with the API query results.
The code example from StackBlitz reproduced here:
import * as React from 'react';
import './style.css';
import { useState, useEffect } from 'react';
import axios from 'axios';
const TOKEN = 'IAPYYRPR0LN9K0K4';
const BASE_URL = 'https://www.alphavantage.co/query?function=GLOBAL_QUOTE';
export default function App() {
const [stockData, setStockData] = useState([]);
const getStocksData = (stock: string) => {
return axios
.get<{ 'Global Quote': { [data: string]: string } }>(
`${BASE_URL}&symbol=${stock}&apikey=${TOKEN}`
)
.then((result) => result.data)
.catch((error) => {
console.error('Error', error.message);
});
};
useEffect(() => {
const stocksList = ['AAPL', 'MSFT', 'TSLA'];
let promises: Promise<void | {
'Global Quote': { [data: string]: string };
}>[] = [];
stocksList.map((stock) => {
promises.push(getStocksData(stock));
});
Promise.all(promises).then((result) => {
setStockData(result);
console.log(stockData);
});
}, []);
return <pre>{JSON.stringify(stockData, undefined, ' ')}</pre>;
}

Related

Usestate not updating

Pretty new to React Hooks and I'm running into a problem with a third party api. I'm getting the data but it's the useState hook isn't updating my state value. I'm pretty sure this is the problem bc I'm getting an error that items.map isn't a function. It does this bc there's nothing in items??? Anyone know how to deal with this?
import React, { useState, useEffect} from "react";
import axios from "axios";
const FeaturedWorks = () => {
const [items, setItems] = useState([]);
const fetchRandomData = async () => {
try {
const res = await axios(
`https://www.rijksmuseum.nl/api/en/collection?key=XXXXXXX`
);
setItems(res.data.artObjects);
console.log(res);
console.log(items);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
fetchRandomData();
},[]);
return (
<div className="featured-container">
{items.map((item, idX) => (
<h5 key={idX}>{item.title}</h5>
))}
</div>
);
};
export default FeaturedWorks;
Here's a screenshot of my response:
Since you're passing [] as the initial state for items, it's already an (empty) array on the first render. If items is not updated, items.map will still work, since .map still exists for an empty array.
So, my guess is that your setItems is indeed updating the state with the result from your query, but res.data is not an array. If res.data is and object like { values: ['foo', 'bar'] }, instead of an array like ['foo', 'bar'], then items will be set to this object and items.map() will in fact throw an error since the object must be an array for the .map function to be defined.
Does you console.log(res.data); really logs an array, or does it log something different?

React, onClick only works after second click, Axios, Async/Await

I must be missing something very straight forward.
I am trying to make an axios GET request and then setState in an async/await function, I have also tried fetch().then(). The console.log() returns undefined unless I click getData() twice or put it in a separate onClick() event. I think the state is being set correctly but the console.log() doesn't perform as I expect it. Is there a way to console.log() in the same function as the axios request? As always, any help is greatly appreciated. Thanks!
import React, { useState } from "react";
import axios from "axios";
const SalesGraph = () => {
const [retrievedData, setretrievedData] = useState();
const getData = async () => {
try {
const salesData = await axios.get("http://localhost:5000/sales");
await setretrievedData(salesData);
console.log(retrievedData);
} catch (err) {
console.log(err);
}
};
const showState = () => {
console.log(retrievedData);
};
return (
<div>
<button onClick={getData}>Graph</button>
<button onClick={showState}>showState</button>
</div>
);
};
export default SalesGraph;
setretrievedData is the asynchronous method and you can't get the updated value of retrievedData immediately after setretrievedData.
You need to get it in the useEffect by adding a dependency.
useEffect(() => {
console.log(retrievedData);
}, [ retrievedData ]);
All setters for react useState are async so even if you await your setretrievedData it will go on to the next line even before it is done.
To run any code only after the setter is done you need to add a useEffect and pass the state to watch as the 2nd arg

Rendered more hooks than during the previous render using useEffect

I have a component with an array of objects, which among other things i am filtering based on a string.
Problem is when I try to set the return of this filter to the local state, it's throwing errors that i am not quite understanding the reason.
import React, { useState, useEffect } from 'react';
import { useQuery } from '#apollo/react-hooks';
import gql from 'graphql-tag'
const ProductsGrid = () => {
const [productsList, setProductsList] = useState([]);
const { loading, data } = useQuery(GET_PRODUCTS);
if (loading) return <div><h4>bla bla bla</h4></div>
const { merchants } = data;
let filtered = [];
merchants.map(merchant => {
merchant.products.map(product => {
if (product.name.includes('Polo')) {
filtered.push(product);
}
})
})
console.log({ filtered });
}
This is printing the following:
So, because I want this array in my state, I decided to do this: setProductsList(filtered);
and what happened after inserting this line was this:
It started rendering multiple times. I assumed that, every time the state changes, it re-renders the component (correct me if im wrong). I don't know why it did it multiple times though.
So, I thought on using useEffect to achieve the expected behaviour here.
useEffect(() => {
console.log('useeffect', data);
if (data) {
const { merchants } = data;
console.log({merchants })
let filtered = [];
merchants.map(merchant => {
merchant.products.map(product => {
if (product.name.includes('Polo')) {
filtered.push(product);
// console.log({ filtered });
}
})
})
console.log({ filtered });
setProductsList(filtered);
}
}, [data])
and the output was it:
So, I am understanding what's going on here and what is this last error about.
I assume my approaching is towards the right direction, using useEffect to run the function only once.
Your problem is due to the useEffect call occurring after the if (loading) condition, which returns early.
Calling hooks after a conditional return statement is illegal as it violates the guarantee that hooks are always called in exactly the same order on every render.
const { loading, data } = useQuery(GET_PRODUCTS);
const [productsList, setProductsList] = useState([]);
if (loading)
return (
<div>
<h4>bla bla bla</h4>
</div>
); // <-- Cannot use hooks after this point
useEffect(/* ... */)
To fix, move the useEffect call to be before the conditional.

react promise in functional component with UseEffect and UseState doesn't work

I'm having issue fetching data and setting them to state in a functional component using useEffect and useState.
My problem is that I would like to keep the data fetching done with axios async/await in a separate file for improving application scalability but then I don't understand how to update the state in case the promise is resolved (not rejected).
In particular I'm trying to retrieve from the promise an array of table rows called data in state, but I can't figure out how to set the result of the responce in the state
Here's the code in the component file:
const [data, setData] = React.useState([]);
useEffect(() => {
const { id } = props.match.params;
props.getTableRows(id).then((res) => {
setData(res);
});
//or is it better:
//props.getTableRows(id).then(setData); ?
}, []);
and my action.js:
export const getTableRows = (id, history) => async (dispatch) => {
try {
const res = await axios.get(`/api/test/${id}`);
dispatch({
type: GET_TEST,
payload: res.data.rows,
});
} catch (error) {
history.push("/test");
}
};
In the above picture it can be seen that the rows array inside the promise response called in action.js is present.
This code unfortunately doesn't work, error: Uncaught (in promise) TypeError: Cannot read property 'forEach' of undefined
I've found out another solution which is the define the promise in the useEffect method like this:
useEffect(() => {
const { id } = props.match.params;
const fetchData = async () => {
const result = await axios.get(`/api/test/${id}`);
setData(result.data.rows);
};
fetchData();
}, []);
this code is working in my app but as I said I don't like having the promises in the components files I would like instead to have them all the promise in action.js for app scalability (in case url change I don't have to change all files) but in that case I don't know where to put the setData(result.data.rows); which seems the right choise in this last example
Any suggestions?
Thanks
You still need to use async/await. The .then() is executed when the value is returned, however your function will continue rendering and won't wait for it. (causing it to error our by trying to access forEach on a null state). After it errors the promise via .then() will update the values and that is why you can see them in the console.
useEffect(() => {
async function getData() {
const { id } = props.match.params;
await props.getTableRows(id).then((res) => {
setData(res);
});
}
getData()
}, []);
Additionally, before you access a state you can check for null values (good practice in general).
if (this.state.somestate != null) {
//Run code using this.state.somestate
}
I don't see you return anything from getTableRows. You just dispatch the result, but hadn't return the res for the function call.
And it will be helpful if you provided error trace.

React Hooks: Referencing data that is stored inside context from inside useEffect()

I have a large JSON blob stored inside my Context that I can then make references to using jsonpath (https://www.npmjs.com/package/jsonpath)
How would I go about being able to access the context from inside useEffect() without having to add my context variable as a dependency (the context is updated at other places in the application)?
export default function JsonRpc({ task, dispatch }) {
const { data } = useContext(DataContext);
const [fetchData, setFetchData] = useState(null);
useEffect(() => {
task.keys.forEach(key => {
let val = jp.query(data, key.key)[0];
jp.value(task.payload, key.result_key, val);
});
let newPayload = {
jsonrpc: "2.0",
method: "call",
params: task.payload,
id: "1"
};
const domain = process.env.REACT_APP_WF_SERVER;
let params = {};
if (task.method === "GET") {
params = newPayload;
}
const domain_params =
JSON.parse(localStorage.getItem("domain_params")) || [];
domain_params.forEach(e => {
if (e.domain === domain) {
params[e.param] = e.value;
}
});
setFetchData({ ...task, payload: newPayload, params: params });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [task]);
}
I'm gonna need to post an answer because of code, but I'm not 100% sure about what you need, so I'll build a correct answer with your feedback :)
So, my first idea is: can't you split your effects in two React.useEffect? Something like this:
export default function JsonRpc({ task, dispatch }) {
...
useEffect(() => {
...
setFetchData(...);
}, [task]);
useEffect(() => {
...
}, [data]);
..
}
Now, if my understanding are correct, this is an example of events timeline:
Due to the update on task you will trigger the first useEffect, which can setFetchData();
Due to the update on fetchData, and AXIOS call is made, which updates data (property in the context);
At this, you enter the second useEffect, where you have the updated data, but NO call to setFetchData(), thus no loop;
Then, if you wanted (but couldn't) put data in the dependencies array of your useEffect, I can imagine the two useEffect I wrote have some shared code: you can write a common method called by both useEffects, BUT it's important that the setFetchData() call is outside this common method.
Let me know if you need more elaboration.
thanks for your reply #Jolly! I found a work around:
I moved the data lookup to a state initial calculation:
const [fetchData] = useState(processFetchData(task, data));
then im just making sure i clear the component after the axios call has been made by executing a complete function passed to the component from its parent.
This works for now, but if you have any other suggestions id love to hear them!

Resources