Call two async functions in a row within React app - javascript

I have two async functions which reach out to API endpoints (Serverless Framework) - one gets and returns a token, the other gets and returns data using the token.
I'm testing these by using simple buttons, where onClick calls the functions to pull the token and the data, respectively. Click one button to get the token, which is saved to state. Then, once I see the token has been received, I click the other button to get the data. This works without any issues at all.
The problem is when I try calling them sequentially from the React app. I need to call these back-to-back when the user submits a request. I can't seem to make the code wait for the token to arrive before trying to pull the data.
The functions being called in the onClick method of the button:
const tokenBtnOnClick = () =>
{
const response = getToken().then(x => {
setToken(x.data.response.token)
})
}
const dataBtnOnClick = () =>
{
const response = getData(token, param1, param2, param3).then(x => {
setData(x.data.response)
})
}
Async functions:
export async function getToken()
{
const apiUrl = `${BASE_URL}/handler/getToken`
const axios = require('axios').default
let response
try
{
response = await axios.get(apiUrl)
}
catch (e)
{
console.log(e)
}
if (response)
{
return response
}
else
{
return ''
}
}
export async function getData(token, param1, param2, param3)
{
const apiUrl = `${BASE_URL}/handler/getData?token=${token}&param1=${param1}&param2=${param2}&param3=${param3}`
const axios = require('axios').default
let response
try
{
response = await axios.post(apiUrl)
}
catch (e)
{
console.log(e)
}
if (response)
{
return response
}
else
{
return ''
}
}
I've tried calling this getBoth() function in a single button's onClick:
async function getBoth()
{
const tokenResponse = await tokenBtnOnClick().then(x => setToken(x.data.response.token))
const dataResponse = await dataBtnOnClick().then(x => setData(x.data.response))
}
But even though it's an async function that uses await on both lines, I always get the same TypeError because dataBtnOnClick is called immediately, without actually waiting for the token to come in. When I run this code, tokenBtnOnClick is called, the app crashes due to a TypeError, and then the token comes in and is logged and saved to state.
I've also tried this: (where getData is exactly as above, but now accepts token as a paramter rather than using the state variable)
async function getBoth()
{
const response = await getToken().then(x => getData(x.data.response.token))
}
index.js?bee7:59 Uncaught (in promise) TypeError: Cannot read
properties of undefined (reading 'then')
How do I get this to actually wait for the token to come in before trying to pull the data?

You are calling setToken and expection token to be updated immediately, but setToken will be asynchronously applied.
Can you useEffect to solve your problem?
useEffect(() => {
getData(token, param1, param2, param3).then(x => {
setData(x.data.response)
})
}, [token])

Try this
const tokenBtnOnClick = () =>{
setToken(getToken())
}
const dataBtnOnClick = () =>{
setData(getData(token, param1, param2, param3))
}
and
const axios = require('axios').default
export async function getToken()
let apiUrl = `${BASE_URL}/handler/getToken`
{
let response = await axios.get(apiUrl)
return response.data.token;
//i don't know exactly what the api returns so it may be diferent
}
export async function getData(token, param1, param2, param3)
{
let apiUrl = `${BASE_URL}/handler/getData? token=${token}&param1=${param1}&param2=${param2}&param3=${param3}`
let response = await axios.post(apiUrl)
return response.data.response;
}
and in your getBoth() just call them because the functions are asynchronous the code will only move forward after them are finished
getBoth(){
setToken(getToken())
setData(getData(token, param1, param2, param3))
}

Related

How to send a GET request with SetTimeout and get data if it is not ready yet?

On Submit click I send a POST request with some data to the server and in response I get an object with id and timeout. Then I need set timeout and when time comes send a GET request for the data. The problem is that data is not ready yet and I get undefined and my React app crashes.
I was told that timeout should be from the first request only (I mean I can't mannualy increase it or do something like this: timeout * 2, I need to use timeout from the first request only). How can I do that? I think it can be done somehow with While loop ...but I'm not smart enough to write this code. Please help
const [someData, setSomeData] = useState({}) // here comes undefined and app crashes because this object renders in DOM
const getData = async (id) => {
const response = await fetch(`$BASE_URL/${id}`)
setSomeData(response)
}
const onSubmit = async (data) => {
const { id, timeout } = await fetch(url, data)
setTimeOut(() => {
getData(id) // data is not ready and I get undefined
}, timeout) // its around 1000ms and I can't change it mannually
}
If I do this then everything works fine
const getData = async (id) => {
const response = await fetch(`$BASE_URL/${id}`)
setSomeData(response)
}
const onSubmit = async (data) => {
const { id, timeout } = await fetch(url, data)
setTimeOut(() => {
getData(id)
}, 6000) // if I manually specify timeout to 6000
}
fetch will return a promises, you could use then to getData.
const onSubmit = async (data) => {
fetch(url, data)
.then(res => return res.json())
.then(res => getData(res.id))
}

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

Use function as react hook?

I wanted to use a function as a react hook to wrap fetch requests to an API.
My current hook:
export function useAPI(url, options={}) {
const [auth, setAuth] = useGlobal('auth');
const [call, setCall] = useState(undefined);
const apiFetch = async () => {
const res = await fetch(url, {
...options,
});
if (!res.ok)
throw await res.json();
return await res.json();
};
function fetchFunction() {
fetch(url, {
...options,
});
}
useEffect(() => {
// Only set function if undefined, to prevent setting unnecessarily
if (call === undefined) {
setCall(fetchFunction);
//setCall(apiFetch);
}
}, [auth]);
return call
}
That way, in a react function, I could do the following...
export default function LayoutDash(props) {
const fetchData = useAPI('/api/groups/mine/'); // should return a function
useEffect(() => {
fetchData(); // call API on mount
}, []);
render(...stuff);
}
But it seems react isn't able to use functions in hooks like that. If I set call to fetchFunction, it returns undefined. If I set it to apiFetch, it executes and returns a promise instead of a function that I can call when I want to in the other component.
I initially went for react hooks because I can't use useGlobal outside react components/hooks. And I would need to have access to the reactn global variable auth to check if the access token is expired.
So what would be the best way to go about this? The end goal is being able to pass (url, options) to a function that will be a wrapper to a fetch request. (It checks if auth.access is expired, and if so, obtains a new access token first, then does the api call, otherwise it just does the API call). If there's another way I should go about this other than react hooks, I'd like to know.
Instead of putting your function into useState, consider using useCallback. Your code would look something like this:
export function useAPI(url, options={}) {
const [auth, setAuth] = useGlobal('auth');
function fetchFunction() {
fetch(url, {
...options,
});
}
const call = useCallback(fetchFunction, [auth]);
const apiFetch = async () => {
const res = await fetch(url, {
...options,
});
if (!res.ok)
throw await res.json();
return await res.json();
};
return call
}
The returned function is recreated whenever auth changes, therefore somewhat mimicking what you tried to do with useEffect

Why is my asynchronous input undefined in useEffect?

I'm writing a React application that fetches image data from a server for an array of URLs. I am storing the camera images as large strings that are placed into the image's src attribute. I am using useReducer to store my dictionary of camera objects.
I am having a couple of problems getting the reducer to work, and one of them has to do with some confusion I'm having with asynchronous values and why the async function returns correct output but the completion handler (.then()) receives undefined as a result.
Here is the code for useEffect() and the asynchronous fetching function.
useEffect()
//Why is cameras undefined?
useEffect(() => {
if (phase === 0) {
let cameras = {}
getCameraInformation().then((cameras) => {
debugger;
dispatch({
type: 'loadedCameraInformation',
payload: {cameras: cameras}
});
}).finally(() => setPhase(1))
}
});
My function signature and variables:
export default function Main() {
const [state, dispatch] = useReducer(cameraReducer, initialState);
let [phase, setPhase] = useState(0);
My function for getCameraInformation:
This returns a dictionary full of correct information!
async function getCameraInformation() {
//returns a json with the following: url, cam_name, cam_pass, cam_user, channel, chunk, group, path, port,
// uptime, username.
let cam_json = await axios
.get(getCamerasURL, { headers: { auth: get_cookie("token") } })
.then(response => {
let tempCameraArray = response.data.body;
let tempCameraDictionary = {};
for (var camera in tempCameraArray) {
tempCameraDictionary[tempCameraArray[camera].sid] = {
cameraInformation: tempCameraArray[camera],
cameraImage: null
};
}
return tempCameraDictionary;
})
.catch(error => console.log(error));
}
Your async function getCameraInformation doesn't have a return statement, so its promise will not resolve any value. There is a return in the then callback, but that's a different function entirely.
You are also using await and then() on the same promise, which isn't ideal. Use one or the other, because it's very easy to get confused when you mix and match here.
You already have an async, so don't use then at all in side that function.
async function getCameraInformation() {
//returns a json with the following: url, cam_name, cam_pass, cam_user, channel, chunk, group, path, port,
// uptime, username.
let response = await axios.get(getCamerasURL, { headers: { auth: get_cookie('token') } })
let tempCameraArray = response.data.body
let tempCameraDictionary = {}
for (var camera in tempCameraArray) {
tempCameraDictionary[tempCameraArray[camera].sid] = {
cameraInformation: tempCameraArray[camera],
cameraImage: null,
}
}
return tempCameraDictionary
}

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