Reactjs Async functions - function not finishing before next one starts - reactjs

I am trying to build a recipe database. I created a function for firebase that allows you to upload a photo to the storage and then get the url for that photo.
const handleUpload = async () => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
console.log(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
setImageUrl(url);
console.log(url);
});
}
);
};
Then I have a form where on the onSubmit, the photo upload function is supposed to fire so it can set the URL in the state, then fire the full upload which uploads the whole recipe to firebase with the url included. It is being handled inconsistently where sometimes it works and sometimes it doesn't. I have been trying different ways to make it work, but nothing seems to make it work perfectly. Here is the current onSubmit function:
onSubmit={async (values) => {
await handleUpload().then(() => {
createRecipe(values, imageUrl)
})
}}
Any help is much appreciated!

React updates state async. You code relies on the fact that the state update setImageUrl(url) will be handled before createRecipe is called.
But there is no guarantee for that. That's basically a race between reacts internal dealings with state update, and your chain of async function.
Better to make handleUpload return a promise to the URL.
const handleUpload = async () => {
return new Promise((resolve, reject) => {
const storage = firebase.storage();
const uploadTask = storage.ref(`images/${image.name}`).put(image);
uploadTask.on(
firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {},
(error) => {
reject(error);
},
() => {
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then((url) => {
resolve(url);
});
}
);
}
};
This way handleUpload will actually return the url when you await it.
onSubmit={async (values) => {
await handleUpload().then((url) => {
createRecipe(values, url)
})
}}
Also note that mixing async/await and then/catch/finally is considered bad practice.
I strongly recommend going with async/await only when you can.
onSubmit={async (values) => {
try {
const url = await handleUpload()
createRecipe(values, url)
} catch (err) {
console.log(err)
}
}}

Related

getDownloadURL takes some time

I falling to get the url return by getDownloadURL appended on the ImageUrls state, getDownloadURL takes a second or two to return the url and it seems like the code continues running and doesn't wait for it to return the url
I am trying to upload multiple images and then create an object in firestore that has the images urls and description
const createAlbum = () => {
addDoc(albumCollection, {
name: albumName,
category: category,
images: imageUrls,
description: description,
});
};
const HandleUpload = (files) => {
files.forEach((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
const uploadTask = uploadBytesResumable(storageRef, file);
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
});
}
);
});
createAlbum();
};
Getting the download URL (like uploading the data, and most modern cloud APIs) is an asynchronous operation. While this call is being handled, your main code indeed continues to execute. Then when the download URL is available, your then callback is invoked with it, so you can use it.
For this reason, any code that needs the download URL needs to be inside the then callback, be called from there, or be otherwise synchronized.
The simplest fix is to move createAlbum into the then callback:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
);
If you only want to call createAlbum() once all uploads are completed, you can keep a counter or use Promise.all():
const HandleUpload = (files) => {
let promises = files.map((file) => {
const storageRef = ref(
storage,
`/files/albums/${albumName}/${file.name}`
);
return uploadBytesResumable(storageRef, file).then(() => {
return getDownloadURL(uploadTask.snapshot.ref);
});
});
Promise.all(promises).then((urls) => {
setImageUrls(urls)
createAlbum();
})
};
This code uses the fact that the task returned by uploadBytesResumable is also a Promise, so we can use then() to know when it's completed, and then gets the download URL.
Note that if setImageUrls is a useState hook, that operation is asynchronous too. I recommend passing the image URLs to createAlbum explicitly, rather than trying to pass it through the state, so createAlbum(urls)
Also you can use promise to error and callback to execute one time createAlbum()
like this:
uploadTask.on(
"state_changed",
(snap) => {},
(err) => {},
(),
error => {
console.log('upload error: ', error.message)
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then((url) => {
setImageUrls((prev) => [...prev, url]);
createAlbum();
});
}
)

Multiple nested axios calls don't resolve as expected

As described in comments between my code snippet, the asynchronicity is not working as expected. For each id, an object/item should return but it only returns one item since my async await isn't implemented properly. What could be a possible workaround?
Thanks in advance
useEffect(() => {
axios.get('url-here').then((res) => {
res.data.favProperties?.map((el) => {
console.log(el) // this returns multitple id's of saved/liked items
axios.get('url-here').then(async (r) => {
if (r.data) {
console.log(r.data) // Problem starts here
// This returns the full object of the liked items
// But only one object is returned, not every object for which an id was stored
await storageRef
.child(r.data.firebaseRef + '/' + r.data.images[0])
.getDownloadURL()
.then((url) => {
// Here i need to fetch the image for each object
console.log(url)
})
.catch((err) => console.log(err))
}
})
})
})
}, [])
I think breaking down your operations into functions will prevent this Promise Hell. I would recommend using async await for these kinda operations. Also I was confused about the last part of console logging the download URL, by my guess you're trying to save all the download URLs for these liked items in an array.
useEffect(() => {
firstFunction();
}, []);
const firstFunction = async () => {
const { data } = await axios.get("url-here");
const favProperties = data.favProperties;
const fetchedUrls = await Promise.all(
favProperties?.map(async (el) => (
await secondFunction(el.id) /** use el to pass some ID */
))
);
};
const secondFunction = async (someId) => {
/** your second URL must point to some ID (or some parameters) specific API otherwise
running same op in a loop without variations doesn't make any sense */
const { data } = await axios.get(`some-other-url/${someId}`);
if (data) {
console.log(data);
const fetchedUrl = await storageThing(data);
return fetchedUrl;
}
};
const storageThing = async ({ firebaseRef, images }) => {
try {
const downloadURL = await storageRef
.child(firebaseRef + "/" + images[0])
.getDownloadURL();
console.log(downloadURL);
return downloadURL;
} catch (error) {
console.log(error);
return '';
}
};

React Native - state is returning null after setting state

I'm very much new to react native currently i'm building small app for just getting an idea about this. I'm facing an issue in mapping the data from API. This is the json response returning from the api
{"data":[{"digit":300,"countsum":"52"},{"digit":301,"countsum":"102"},{"digit":302,"countsum":"27"},{"digit":303,"countsum":"201"},{"digit":500,"countsum":"101"}]}
When i tried to map this data i'm facing some issues. I stored the response from API to the state and when i tried to display the state data using map function it's showing the state value is null. This the code i tried till now
const [listdata, setListData] = useState(null)
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
}
Do it like this,
export default function ComponentName () {
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>
);
}
You have to wait the fetch execution and later do the list map.
// wait for it
await axios
.get(constants.BASE_URL + "getlist?token=" +token)
.then(response => setListData(response.data))
.catch(error => {
console.log(error);
});
listdata.map(item => <Text>{item.digit}</Text>)
If you want to map the data then do that inside return statement of your code ,like:
return(
{listData?listdata.map(item => return <Text>{item.digit}</Text>):""}
);
This is a sample of a meant in my comment above:
Try console.log listdata at this stage, you will find that it is still
null, in other words, the value of the updated value of the
listdata:useSate will be ready after the render take place. You can
make another function outside of the current one. then use useEffect
with listdata to update your text views
const [listdata, setListData] = useState(null)
useEffect(() => makeRemoteRequest(), [listdata])
makeRemoteRequest = () => {
const url = `your-url-of-data-here`;
fetch(url)
.then(res => res.json())
.then(res => {
setListData(res.data);
})
.catch(error => {
console.log(error)
});
};
You could try the following:
const [listdata, setListData] = useState([])
useEffect(() => {
// Run! Like go get some data from an API.
getListData();
}, []);
const getListData = async () => {
const token = await AsyncStorage.getItem("#userToken")
try {
const dataResponse = await axios.get(constants.BASE_URL + "getlist?token=" +token);
setListData(dataResponse.data || [] );
} catch(error) {
console.log(error);
}
}
return (<>
listdata.map(item => <Text>{item.digit}</Text>)
</>);

How to use async function and export it correctly with React Native?

My question is about correctly implementing an async function to fetch data. I've a function called _getData() and I'm calling it on the componentDidMount() of a screen. But when server response is slow, switching to this screen is getting slower. So I would like to use async function for fetching data. But I'm not sure if I'm doing it correctly. Is that a correct approach? I can't be sure if it works async or not.
Here is my Api._getData() code:
const _getData = async () => {
return await axios.get("http://blabla.com/someservice", { params: someParamDataHere });
};
export const Api = {
_getData
};
and on SomeScreen.js, I also have loadData() function which calls the function above and does state updates.
loadData() {
Api._getData()
.then((response) => {
this.setState({ myData: response.data });
})
.catch((error) => {
console.log(error.response);
});
}
in componentDidMount() function of the same screen I'm calling this loadData()  function directly.
Now, is it enough to declare Api._getData() as async and using await in it, or should I change some trigger functions too?
Thank you very much for your help.
instead of async await use promises
export const getRequest = (url) => {
return new Promise((resolve, reject) => {
api
.get(url)
.then((response) => {
handleReponse(response)
.then((errorFreeResponse) => {
resolve(errorFreeResponse);
})
.catch((error) => {
reject(error);
});
})
.catch((error) => {
reject(handleError(error));
});
});
};
You are doing correct while retrieving in load Data . What you can do more is try more syntactical sugar of es6 by using async await in loadData , hence
loadData = async() =>{
try{
let response = await Api._getData();
this.setState({ myData: response.data });
} catch(err){
console.log(error.response);
}
}
Hope it helps. feel free for doubts

Update react-native state using async/await

I am trying to immediately update a boolean state variable in react native. the await keyword does not work however. because state setter functions are asynchronous, how can I do this using async / await? In vscode, the await in front of the setLike setter function has a message : "await has no effect on this type of expression"
const likeIt = async () => {
console.log('like pressed');
console.log('liked? before set', like); //**false**
await setLike(like => !like);
console.log('liked? after set', like); //**should be true but getting false**
const axiosAuth = await axiosWithAuth();
const userToken = axiosAuth.defaults.headers.Authorization;
if (like) {
axiosAuth.post(`https://url`,{})
.then(res => {
console.log('response from post like: ', res.data);
})
.catch(err => console.log('error in post like', err.message))
} else {
axiosAuth.delete(`https://url`)
.then(res => console.log('res from unlike', res.data))
.catch(err => console.log('err from unlike', err))
}
}
If we talk about react hooks, you should to know that useState() return array of two values. The second value is a dispatch function which we use to change value of state. And this functions are synchronous.
In your example this dispatch function is setLike() (and it is synchronous function). So await keyword do not work for them actually.
React has special system in the hood to work with changing state. This system wait for batch of changing states and then update our state and rerender component. Before that update of state and rerender of component, our likeIt() function will be done.
You could use useEffect() lifecycle method to handle change of like state.
For example:
const likeIt = async () => {
console.log('like pressed');
console.log('liked? before set', like); //**false**
await setLike(like => !like);
}
useEffect(() => {
console.log('liked? after set', like); //**true**
...
}, [like]);
await useState(); will not work.
Here is a possible solution that rather works with a temp variable in your likeIt() function.
function App() {
const [like, setLike] = useState(false);
const likeIt = async () => {
let temp = !like;
const axiosAuth = await axiosWithAuth();
const userToken = axiosAuth.defaults.headers.Authorization;
if (!temp) {
axiosAuth.delete(`https://url`)
.then(res => console.log('res from unlike', res.data))
.catch(err => console.log('err from unlike', err))
} else {
axiosAuth.post(`https://url`,{})
.then(res => {
console.log('response from post like: ', res.data);
})
.catch(err => console.log('error in post like', err.message))
}
setLike(temp);
}
return (
<div className="App">
<button onClick={likeIt}>{like === true ? 'Liked' : 'Not Liked'}</button>
</div>
);
}
Are you getting compiling errors due to await setLike()?
If not, it is a small issue that would be confusing for some people on VScode.
Please kindly check https://github.com/microsoft/vscode/issues/80853

Resources