When I try to proces data come from api then use it to render, but I always go to a problem with async because the process function doesn't wait for my fetching functions.
const [fetchData1, setData1] = useState([]);
const [fetchData1, setData2] = useState([]);
const [processedData, setProcessedData] = useState([]);
useEffect(() => {
const getData1 = async () => {
//get data1 using axios
//setData1(response)
}
const getData2 = async () => {
//get data2 using axios
//setData2(response)
}
getData1();
getData2();
setProcessedData(processData(fetchData1, fetchData2));
}, [])
const processData = (data1, data2) => {
//process two data
//return data;
}
Even when I try to wrap two fetching functions and the process function in an async function but the problem remains the same.
(async () => {
await getData1();
await getData2();
setProcessedData(processData(fetchData1, fetchData2));
})
Reading your question, as far as I can tell you don't need fetchData1 and fetchData2, you just want the processedData. The problem with your current code is that it's using the default values of fetchData1 and fetchData2 when calling setProcessedData, it's not using the results form axios.
Wait for both promises to settle and use their results. See comments:
const [processedData, setProcessedData] = useState([]);
useEffect(() => {
const getData1 = async () => {
//get data1 using axios
//setData1(response)
};
const getData2 = async () => {
//get data2 using axios
//setData2(response)
};
// *** Wait for both promises to be fulfilled
Promise.all(
getData1(),
getData2()
).then([data1, data2]) => { // Get those results into parameters
// *** Use the parameter values
setProcessedData(processData(data1, data2));
}).catch(error => {
// handle/report error
});
}, []);
// *** render using the current values in `processedData`
Note that since you're only do this when the component is first created, you don't need to worry about cancelling it, etc., when other state in the component changes (if it has other state). If the calls depended on other state data you were listing in the dependency array, you might need to handle disregarding earlier results if that other data changed during the calls to axios. But again, not with what you're doing here.
Promise.all is for handling multiple asnyc operations:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Here is more examples:
https://www.taniarascia.com/promise-all-with-async-await/
Related
I am doing get request to backend to fetch data from database,
Im doing something like :
const loadData = async () => {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
};
useEffect(() => {
loadData();
console.log(OrdersData)
}, []);
when i console.log(OrdersData) it console.log 6 times thus in rendering the data it rendering it 6 times as well, i also tried to set dependency in useEffect like as follow:
const loadData = async () => {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
};
useEffect(() => {
loadData();
setLoading(false)
console.log(OrdersData)
}, [loading]);
But still when i render OrdersData it rendering it 6 times even though the response result is only one object, i couldn't figure it out how to not duplicate the data.
To prevent unnecessary renders try to use the useCallback hook in the loadData as so:
const loadData = useCallback(async () => {
try {
const response = await fetch(URL);
const data = await response.json();
setOrdersData(data.data);
} catch (err) {
//do something
}
}, [])
Remember to import as hook, and use the dependecies as you please like useEffect or useMemo.
Hoping it works remember also to unmount the the side effects in useEffect with return statement
I'm developing the front-end for my spring boot application. I set up an initial call wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
The data returned isn't comprehensive, and needs further call to retrieve other piece of information, for example this initial call return an employee id, but if I want to retrieve his name and display it I need a sub-sequential call, and here I'm experiencing tons of issues:
const getEmployeeName = async id => {
try {
const name = await fetchContext.authAxios.get(
'/employeeName/' + id
);
console.log((name["data"])); // <= Correctly display the name
return name["data"]; // return an [Object promise],
} catch (err) {
console.log(err);
}
};
I tried to wrap the return call inside a Promise.resolve() function, but didn't solve the problem. Upon reading to similar questions here on stackoverflow, most of the answers suggested to create a callback function or use the await keyword (as I've done), but unfortunately didn't solve the issue. I admit that this may not be the most elegant way to do it, as I'm still learning JS/React I'm open to suggestions on how to improve the api calls.
var output = Object.values(data).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Async functions always return promises. Any code that needs to interact with the value needs to either call .then on the promise, or be in an async function and await the promise.
In your case, you should just need to move your code into the existing useEffect, and setState when you're done. I'm assuming that the employeeID is part of the data returned by the first fetch:
const [name, setName] = useState('');
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
"/myapi/" + auth.authState.id
);
setData(data);
const name = await fetchContext.authAxios.get(
'/employeeName/' + data.employeeID
);
setName(name.data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
// ...
var output = Object.values(appointmentsData).map((index) =>
<Appointment
key={index["storeID"].toString()}
// other irrelevant props
employee={name}
approved={index["approved"]}
/>);
return output;
Note that the above code will do a rerender once it has the data (but no name), and another later when you have the name. If you want to wait until both fetches are complete, simply move the setData(data) down next to the setName
I'm trying to set a variable with a simple GET database call. The database call is returning the data correctly, but the variable remains undefined after every re-render. Code is below... the getMyThing() function in the useState() function is working correctly and returning the data I want.
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(getMyThing(id));
useEffect(() => {
setMyThing(myThing)
}, [myThing]);
}
My thinking here was to use useState() to set the initial state of the myThing variable with the data returned from my database. I assume it's not immediately working since a database call is asynchronous, so I thought I could use useEffect() to update the myThing variable after the response of the database call completes, since that would trigger the useEffect() function because I have the myThing variable included as a dependency.
What am I missing here? Thanks!
EDIT: Thanks for the answers everyone, but I still can't get it to work by calling the getMyThing function asynchronously inside useEffect(). Is something wrong with my database call function? I guess it's not set up to a return a promise? Here's what that looks like:
export const getMyThing = (id) => {
axios.get('http://localhost:4000/thing/' + id)
.then(response => {
return(response.data);
})
.catch(function (error){
console.log(error);
})
}
You should do all your side effects(fetching data, subscriptions and such) in useEffect hooks and event handlers. Don't execute async logic in useState as you just assign the promise itself to the variable and not the result of it. In any case, it is a bad practice and it won't work. You should either:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
const fetchData = async () => {
const result = await getMyThing(id);
setMyThing(result);
};
fetchData();
}, [id, getMyThing]);
}
Or if you don't want to introduce an async function:
import { getMyThing } from '../../utils/databaseCalls'
const MyComponent: React.FC = () => {
const { id } = useParams();
const [myThing, setMyThing] = useState(null);
useEffect(() => {
getMyThing()
.then(result => setMyThing(result));
}, [id, getMyThing]);
}
Also, take note of the [id, getMyThing] part as it is important. This is a dependency array determining when your useEffect hooks are gonna execute/re-execute.
If getMyThing returns a Promise, the myThing will be set to that Promise on the first render, and then myThing will stay referring to that Promise. setMyThing(myThing) just sets the state to the Promise again - it's superfluous.
Call the asynchronous method inside the effect hook instead:
const [myThing, setMyThing] = useState();
useEffect(() => {
getMyThing(id)
.then(setMyThing);
}, []);
Here, myThing will start out undefined, and will be then set to the result of the async call as soon as it resolves.
You can't set the initial state with a value obtained asynchronously because you can't have the value in time.
myThing cannot both return the value you want and be asynchronous. Maybe it returns a promise that resolves to what you want.
Set an initial value with some default data. This might be null data (and later when you return some JSX from your component you can special case myThing === null by, for example, returning a Loading message).
const [myThing, setMyThing] = useState(null);
Trigger the asynchronous call in useEffect, much like you are doing now, but:
Make it rerun when the data it depends on changes, not when the data it sets changes.
Deal with whatever asynchronous mechanism your code uses. In this example I'll assume it returns a promise.
Thus:
useEffect(async () => {
const myNewThing = await getMyThing(id);
setMyThing(myNewThing)
}, [id]);
I am sending api requests to a database in order to receive data inside a widget, perform a little bit of processing on the data, then plot it and render it to my web page. This is being done in a react web app.
I am using fetch to get the data and receiving a promise. The fetching is being done inside a function which I will later call multiple times. I have found examples of how to directly console.log the data from fetch promises when making api calls, but doing this inside a function always seems to result in a promise for me... I have no idea how to return the data!
Any help that can be provided would be very much appreciated. My current code is below and returns a promise.
Thank you.
class MyWidget extends Component {
constructor(props) {
super(props)
}
async fetchData(url) {
try {
const response = await fetch(url);
const data = await response.json();
return data;
} catch (error) {
console.error(error);
}
}
async readDB(url) {
const data = await this.fetchData(url);
return data
}
processingFunction() {
const url = 'my/database/api/variable=X'
const database_data = this.readDB(url)
// perform processing on database data...
// plot processed data...
}
You have to await in the processingFunction which also has to be changed to async
async processingFunction() { // change to async
const url = 'my/database/api/variable=X'
const database_data = await this.readDB(url); // await the async result
// perform processing on database data...
// plot processed data...
}
but it seems that you don't need the readDB func at all
async processingFunction() {
const url = 'my/database/api/variable=X'
const database_data = await this.fetchData(url);
}
UPDATE
You cannot convert Promise into Array or else. There is a certain flow in React: state + life-cycle hooks. In this case you have to store data in state and use componentDidMount hook
import React, {Component} from 'react'
class MyWidget extends Component {
state = {
dataFromDB: null // set initial state
};
processingFunction() {
const yourData = this.state.dataFromDB;
// perform processing on database data...
// plot processed data...
}
async componentDidMount() {
const url = "my/database/api/variable=X";
const database_data = await this.fetchData(url);
this.setState(() => ({
dataFromDB: database_data // set data in state from DB
}));
}
...
}
So, I know this question has been asked 100's of times, but none of the solutions seems to work in my instance.
I am using useState hook to update state to a value initialValues that gets data returned from a getInitialValues function
const [initialValues, setInitialValues] = useState(getInitialValues());
The getInitialValues function does a logic check and either returns an object or another function retrieveDetails()
const getInitialValues = () => {
let details;
if(!addressDetails) {
details = retrieveDetails();
} else {
details = {
...,
...,
...
};
}
return details;
}
The function, retrieveDetails is an async function that makes an API call, and I await the response and return the object received from the response.
const retrieveDetails = async () => {
const addr = addressDetails[currentAddress];
const { addressLookup } = addr;
const key = process.env.API_KEY;
const query = `?Key=${key}&Id=${addressLookup}`;
const addrDetails = await new AddrService().getAddressDetails(query);
return addrDetails;
}
However, when I log the state initialValues it returns PromiseĀ {<pending>}?
Even removing the API call and simple returning an object in it's place renders the same result.
Not sure the best way around this to actually return the object?
Any help would be greatly appreciated.
I don't think there is a way to get initial data to useState asynchronously, at least not yet.
React is not waiting for your data to arrive, the function will keep on running to completion while your async operation is queued (on the event loop side).
The current idiomatic way is to fetch the data in an effect and update the state.
useEffect(() => {
getData(someParam).then(data => setState(data))
}, [someParam])
You can read more about it in the DOCS
This isn't something React's built-in hooks support.
You need to build or import a custom hook.
Want short and simple? Try this:
const addressDetails = useAsyncFunctionResult(retrieveDetails);
and add this in hooks/useAsyncFunctionResults.js
function useAsyncFunctionResult(asyncFunction, dependencies = []) {
const [result, setResult] = React.useState();
React.useEffect(() => {
let mounted = true;
asyncFunction().then((data) => mounted && setResult(data))
return () => { mounted = false; }
}, dependencies]);
return result;
}
Here the retrieveDetails function (the one from the question) will start executing (the ().then bit above) when the hook is first called. The result is kept until unmount. And we get no errors about changing component state after unmounting.
If you later want to add caching, use existing hooks instead if making your own.
There's no official useAsync in React, and likely never will be, because if your Promise did a request and the requesting component did unmount before the promise resolves, then best practice is to cancel which differs case by case.
const retrieveDetails = async () => {
const addr = addressDetails[currentAddress];
const { addressLookup } = addr;
const key = process.env.API_KEY;
const query = `?Key=${key}&Id=${addressLookup}`;
const addrDetails = await Promise.resolve(new AddrService().getAddressDetails(query))
return addrDetails;
}
**try this once changed the function a bit**