Axios request returning promise Object - reactjs

Not sure why when I log out the price after executing the fetchPrice() method, I am returned a promise object instead of price. Please help :)
async function fetchPrice() {
await axios
.get(assetData(selectedAsset))
.then((response) => {
return response.data.market_data.current_price.eur;
})
.catch((error) => console.log(error.response.data.error));
}
useEffect(() => {
let price = fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);

The problem is how you are handling the await of your function. Normally when working with promises you use either await or a callback, not both. Because await already wait for the promise to resolve or throw an error, you directly use the return inside the function and not in the callback.
async function fetchPrice() {
try {
const price = await axios.get(assetData(selectedAsset));
return price;
} catch (e) {
console.log(error.response.data.error)
}
}
Using return inside the callback doesn't return the object you expect since it is the result of your callback function and not the original function.
Since fetchPrice is an async function it is normal that if you try to print it you will obtain a promise. This won't change even if you correct your function as I told you above. The only way to obtain the object is by awaiting your fetch price inside the useEffect (and by making the useEffect itself async) like this:
useEffect(async () => {
let price = await fetchPrice();
console.log("Asset Price: " + price);
let assetTotal = Number(cost) / Number(price);
setAssetAmount(assetTotal);
}, [selectedAsset]);
However, if you do it, you will obtain the following warning because your use effect use should be synchronous:
Effect callbacks are synchronous to prevent race conditions. Put the async function inside
The final solution? Set state directly inside the callback.
async function fetchPrice(cost) {
try {
const price = await axios.get(assetData(selectedAsset));
setAssetAmount(Number(cost)/Number(price))
} catch (e) {
console.log(error.response.data.error)
}
}
However, be careful of memory leaks when setting a state asynchronously inside a component that can be unmounted.

You need to use either .then() or async/await.
Example of then()
useEffect(() => {
axios.get("https://api.github.com/users/mapbox").then((response) => {
console.log(response.data);
});
}, []);
In above code, we are using .then() so then will be fired when api call is done.
Example of async/await
async function axiosTest() {
const response = await axios.get("https://api.github.com/users/mapbox");
console.log(response.data, "with await");
}
In above code, we are using await to wait until the api call is done.
Here is the Codesandbox to check the difference with live api call

Related

async await not working as expected with react

I am calling an async function inside useEffect hook in react as below.
const ab = async () => {
console.log("start API");
await callAPI();
console.log("end API");
};
useEffect(() => {
ab();
console.log("calling");
}, []);
As the output first it will print start API then print calling and finally print end API. But what I need as the printing order is start API, end API, and calling. That's why I am using async await as well. But it's not working as expected. Any way to fix this issue?
That's the point of using await – the execution is paused until callAPI is finished.
To achieve the order you want, don't use await and just let the code run asynchronously. This way, the synchronous code (console.log('calling')) will be ran without waiting for the asynchronous code (callAPI()) to finish.
EDIT: However, depending on your use case, the other answer would probably make more sense.
For async/await to work , you'll have to await an asynchronous function.
Hence use the following code.
useEffect(() => {
let helper = async () => {
await ab(); // write await here
console.log('calling');
};
helper();
}, []);
Now the output would be:
Start API
End API
calling

What's the best practice of calling API data from a function outside useEffect?

While working with react useEffect hook, most of the example I came across in case of calling api data in useEffect hook for initiate the component is, calling api directly inside useEffce hook.
For instance,
useEffect(() => {
async function(){
const res = await axios.get(`https://jsonplaceholder.typicode.com/${query}`);
setData(res.data)
}
}, []);
But what about fetch data outside the hook with a method ? For instance,
const getData = () => {
async function(){
const res = await axios.get(`https://jsonplaceholder.typicode.com/${query}`);
setData(res.data)
}
useEffect(() => {
getData(); // here eslint shows an warning "Promise returned from setData is ignored"
}, [])
is there any specific reason for avoiding second example. If not what's the proper way to call api call function in useEffect hook with proper cleanup ?
In React component file
useEffect(() => {
loadData(query).then(setData)
}, [query])
crate another service file to serve data from API
in service file
export const loadData = async query => {
const res = axios.get(`https://jsonplaceholder.typicode.com/${query}`);
return res.data;
// You can put this API call in try catch to handle API errors
};
Creating a separate function for calling an api is a perfect example of loading data in useEffect. You can give it parameters if you would have a paginated endpoint and call it multiple times or add polling to the page to load the data by interval. I can only see benefits by creating a function for this.
useEffect(() => { fetch("./product.JSON") .then(res => res.json()) .then(data => setProducts(data)) }, [])

Why does my axios call return undefined even after then is resolved?

I'm currently using functions to predefine all of my axios calls so for example:
export const getClients = () => {
axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Now, I want to call this in a class-based component in the componentDidMount like this:
componentDidMount(){
this.setState({
clients: getClients()
});
}
I can't seem to figure out why when I try to console.log(this.state.clients) at the end of componentDidMount I'm getting an undefined error. I'm new to React and from what I understand, the then in the function of the axios call should resolve the promise and return the actual response from the API call so when I call getClients(), the clients state should be the response.
What am I doing wrong?
componentDidMount(){
fetchClients();
}
const fetchClients = () => {
getClients().then( (response)=> {
// handle success
this.setState({clients:response});
});
};
Okay there is some stuff that needs to be cleared out here :-
You need to change getClients like so :-
export const getClients = () => {
return axios.get("/client/")
.then(response=>{
return response;
})
.catch(error=>{
return error;
});
};
Why ?
Because the value that you returned from the callback of then is wrapped inside a Promise implicitly and has to be returned for consumption as you do in a function like function sum(a,b) {return a+b}
componentDidMount will change like so :-
componentDidMount(){
const fetchClients = async () => {
const clients = await getClients();
this.setState({clients});
}
fetchClients();
}
Why ?
If you want to use getClients in a statement fashion instead of .then().then() promise chain, you will first wrap it inside an async function and then call await on getClients() (remember this function returns a Promise) and then set the state inside that async function.
Even if you console.log the clients state after fetchClients() within componentDidMount, you probably won't see the value set because setState works asynchronously. So never rely on the console.log of your state just after setting it.

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.

How to access data after asynchronous api call using react/ redux saga ? More info below

Tech using: React, Redux, Saga
how do I access data right after from second function call? The firstFunction keeps executing the rest of the code. I tried async/await, but it does not work.
firstFunction (()=>{
secondFunctionAPI() //using Redux and Saga
// Here I want to use the data from the response of secondFunction().
more code...
more code..
})
You could pass the code you want to execute as a callback to secondFunctionAPI
firstFunction(() => {
secondFunctionAPI((data) => {
// Code using data object
})
})
secondFunctionAPI would look something like that:
secondFunctionAPI = (callback) => {
API.fetchSomething().then(response => {
// Call back with data object
callback(response.data);
})
}
You could invoke second function by dispatching an action after api call like below:
const response = yield call(firstFunction);
yield put({ type: 'SOME_ACTION', payload: response.data });
Then have a watcher saga defined which will wait for action of type 'SOME_ACTION' and call the handler once this action is dispatched.
yield takeLatest('SOME_ACTION', second function)
And then define your handler for 'SOME_ACTION' like:
function* secondFunction(action) {
// here action is the object which was sent as param in put() method earlier
const { type, payload } = action;
// your logic here
}
Approach 1: Callbacks
Like others have noted, you can make secondFunctionAPI take in a callback parameter, then call it:
firstFunction (() => {
secondFunctionAPI(() => {
// More code here...
});
});
Approach 2: Promises
Convert both functions to use promises, like so:
function secondFunctionAPI(){
return new Promise((resolve, reject) => {
// do stuff, then:
resolve();
});
}
Then, call your functions like this:
firstFunction().then(() => {
secondFunctionAPI().then(() => {
// More code here...
});
});
Promise.all
If you can call both functions simultaneously (secondFunctionAPI does not need the result of firstFunction to run), you can use this for efficiency:
Promise.all([
firstFunction(), secondFunctionAPI()
]).then(() => {
// ...
});
Approach 3: async/await
Make your functions return promises, like above.
Make a wrapper async function.
async function doStuff(){
try { // This is how you would catch rejections
await firstFunction();
} catch(error) {
console.error(error);
}
var foo = await secondFunctionAPI(); // This is how you get the result
}

Resources