I'm currently coding a function to save the blogPost object into a document in my Firestore database.
The intended routine should be:
- Click on SAVE button and call the
savePost()function - The
savePost()function should call theuploadImagesAndGetURL()function because the images of theblogPostshould be uploaded to Firebase Storage before I save theirsrcURL to the Firestore. (in my real case the image upload isasyncand Iawaitfor it to complete, but I don't think it hurts the example here). - The
uploadImagesAndGetURL()should upload the images and store the returnedurlsfor each one in their respectivesrcproperty, inside theelementsarray of thestate. - It does that by running a
forEach()on theelementsarray and callingsetState()(setBlogPost) to update state with the new URL's received from the storage. - The
saveToFirestore()function will then be called and should be able to access the most recent state to be able to save theblogPostobject along with the imagesrc'sto the database.
QUESTION
My problem is that while I'm running the savePost() function the component is being re-rendered 3 times in this case (for 3 images), because the uploadImagesAndGetURL() is calling setBlogPost() 3 times. One for each image. And when I reach the saveToFirestore() function and I log the blogPost state, you can see that only the first image src has changed.
Am I doing it right? How can I improve this routine to make sure everything has been updated before I saveToFirestore()?
NOTE: One way to do it would be to skip the state update and pass the returned URLs straight to the saveToFirestore() function without ever storing them on the state. But, what is the appropriate pattern in this situation?
Log from the saveToFirestore() function:
{
"title": "This is the title",
"body": "Click on the button to upload this post",
"elements": [
{
"type": "image",
"src": "source0"
},
{
"type": "image",
"src": null
},
{
"type": "image",
"src": null
}
]
}
SNIPPET BELOW
function App() {
console.log('Rendering App...');
const [blogPost,setBlogPost] = React.useState({
title: 'This is the title',
body: 'Click on the button to upload this post',
elements: [
{ type: 'image',
src: null
},
{ type: 'image',
src: null
},
{ type: 'image',
src: null
}
]
});
function savePost() {
console.log('Inside savePost');
uploadImagesAndGetURL();
saveToFirestore();
}
function uploadImagesAndGetURL() {
console.log('Inside uploadImages');
blogPost.elements.forEach((item,index) => {
console.log('Inside uploadImages - forEach');
setBlogPost((prevState)=>{
const aux = Array.from(prevState.elements);
aux[index].src = 'source' + index;
return({
...prevState,
elements: aux
});
});
});
}
function saveToFirestore() {
console.log('Inside saveToFirestore');
console.log('Will save the following blogPost to Firestore');
console.log(blogPost);
}
return(
<React.Fragment>
<div>{blogPost.title}</div>
<div>{blogPost.body}</div>
<div>Image 1 src: {blogPost.elements[0].src}</div>
<div>Image 2 src: {blogPost.elements[1].src}</div>
<div>Image 3 src: {blogPost.elements[2].src}</div>
<button onClick={savePost}>Save Post</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>