Function to store fetch api call as local variable (not promise) - javascript

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
}));
}
...
}

Related

How to access data outside of a Promise

I'm making a react app that sends an API call to OpenWeather to get the weather data for a city (specified by the user). Here's what the request for that call looks like:
async function getAPI() {
const apiCall = await axios.get(apiLink).then(res => {
res = {
temp : res.data.main.temp - 273.15,
weatherIcon : res.data.weather[0].icon,
windSpeed : res.data.wind.speed
}
return res
});
return apiCall
}
const weatherData = getAPI()
Notice that I try to store the data I want from the API response in a variable called weatherData. That way I can simply call that variable whenever I need, heres an example of HTML code that uses this variable:
<p>
temperature is {weatherData.temp} Celcius
</p>
This results in weatherData.temp simply not showing up on the browser side for some reason. A console.log(weatherData) prints this in the console:
Promise {<pending>}
[[Prototype]]: Promise
[[PromiseState]]: "fulfilled"
[[PromiseResult]]: Object
temp: 29.53
weatherIcon: "04d"
windSpeed: 1.59
[[Prototype]]: Object
How do I extract the data from the promise in a way that allows me to easily refer to said data for use in HTML code?
Answer below is if you are using functional components and react hooks.
You can can go two directions:
Using a try catch block:
const fetchWeather = async () => {
try {
const res = await axios.get(apiLink);
console.log(res);
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// you can then set the data you need to your state to render it.
} catch (error) {
// handle error
}
}
Or you can use .then .catch
const fetchWeather = async () => {
axios.get(apiLink)
.then((res) => {
setWeather(res.data); //Im not sure what the exact response is, but you can access the keys you need.
// set the data you need from the respones to your state.
})
.catch((err) => {
// handle error
})
}
In both cases you can just call the function in your useEffect hook.
useEffect(() => {
fetchWeather()
}, [])
In general my preference goes to set the response you get from the Api into the local state (meaning the state of your page/component). And then rendering the state to your jsx.
So if you are using react hooks, your state could look like this:
const [weather, setWeather] = useState({});
Last Edit:
Finally you can just refer to your state within your jsx/html. Assuming your weather state looks like this:
{
temp: '50 degrees'
}
In your JSX you can just refer to it this way:
<>
<div>{weather.temp}</div>
</>

How to get data from two different api routes in nextjs?

I'm using Next.js and I need to get data from two different API routes. I want to fetch the data in getServerSideProps.
The first data I need is from the http://localhost:3000/api/admin/classes/${className} route.
The second set of data will be from http://localhost:3000/api/admin/classes/${className}/subjects this route.
When I try to get data from just a single API, it works fine. I tried to fetch the data from both API using the code in getServerSideProps. But it doesn't work.
I want to have the data like this export default function classPage({ subjects, classDetail }) {}. The return props from gerServerSideProps should look like this: return { props: {classDetail: data, subjects: data2} }, if it's possible
export async function getServerSideProps({ query: { className } }) {
const res = await fetch(
`http://localhost:3000/api/admin/classes/${className}`
).then(() => {
const res2 = await fetch(`http://localhost:3000/api/classes/${className}/subjects`)
});
const { data } = await res.json();
const {data2} = await res2.json()
return { props: { classDetail: data } };
}
Api get request code:
try {
const subjectDetail = await Subject.find({}).populate('classDetail')
res.status(200).json({success: true, data: subjectDetail})
} catch (error) {
res.status(400).json({success: false})
console.log(error)
}
You can do it much simpler, I assume that you don't need to wait for the first request to end to starts the second so you can simply use Promise.all to wait for both requests to finish.
export async function getServerSideProps({ query: { className } }) {
// Create the promises for the data we need to fetch
const promises = [
fetch(`http://localhost:3000/api/admin/classes/${className}`).then(res => res.json()),
fetch(`http://localhost:3000/api/classes/${className}/subjects`).then(res => res.json()),
];
// Wait for all the promises to resolve and get the data
const [classDetail, subjects] = (await Promise.all(promises)).map(p => p.data);
return { props: { classDetail, subjects } };
}
But the problem that you seem to have with the second request is that when you write: const {data2} = await res2.json(), you are trying to get the attribute data2 from the response which is probably not what you want. You need to get data from both responses as I did here.

React: String automatically converted to [object promise] when called from another component

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

How to process fetched data in useEffect

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/

await/async axios call being called before response from previous call is done

I have a React component that calls an async function in componentDidUpdate. Inside that function, I have an array of items that I call Promise.all on. There is one condition where an axios call is made depending on what gets returned from a previous axios call. The problem I am having is that the axios call is made before the results from the previous axios call is finished, and I am not sure why that is happening.
Here is my code:
class Test extends Component {
this.state = {
experiments: []
}
async componentDidMount() {
await this.getExperiments(); // function to fetch experiments from a db
}
async componentDidUpdate() {
if (condition) {
await myFunction()
}
}
myFunction = async () => {
try {
const { experiments } = this.state;
const results = await Promise.all(experiments.map(async experiment => {
const firstAxiosCall = await axios.get(someUrl);
const secondAxiosCall = await axios.get(anotherUrl)
const { data } = secondAxiosCall; // THIS IS WHERE BUG OCCURS
if (data.length === 0) { // For one experiment, this is not empty, but it still goes into the if statement.
await axios.post(thirdUrl)
}
}));
} catch (e) {
console.log('ERROR', e);
}
}
}
I know that this is a bug because the axios call inside the if statement is called and I get a db error on my backend saying that nothing was passed in. I want the data from the second axios call to return first before proceeding the if statement. Is there something that I am doing wrong?
I hope this is enough information!
Thank you all!
I'd suggest you to use something like so:
myFunction = async () => {
await axios.all([
axios.get('http://someurl.com'),
axios.get('http://anotherurl.com')
])
.then(axios.spread((someUrlRest, anotherUrlRes) => {
// do something with both responses
});
}

Categories