Issue
I'm currently building a React component in which user can upload images. In this simplified example, I want to upload 3 Images. When the upload of each file finishes, I want to add this file to my list of uploaded files.
Problem: Because the setter method of useState is not synchronous, we only add one of three uploaded files to the list. The other two additions become overwritten by the last uploading file.
Here the example:
const Example = () => {
const defaultValues = ["aaa.jpg", "bbb.jpg", "ccc.jpg"];
const [uploadedFiles, setUploadedFiles] = React.useState([...defaultValues]);
const uploadFunction = () => {
//images I want to upload
const selectedImagesToUpdate = ["ddd.jpg", "eee.jpg", "fff.jpg"];
//iterate over list of images, which I want to upload
selectedImagesToUpdate.forEach(item => {
//upload image
uploadImage(item).then(response => {
//on response, add uploaded item to myList
console.log("add item ", response);
setUploadedFiles([...uploadedFiles, response]); //<-- here is my problem
})
});
}
const uploadImage = (image) => {
//blackbox for uploading an image async with axios like:
//return axios.post(url, image);
return Promise.resolve(image);
}
return (
<div>
<p>UploadedFiles:{JSON.stringify(uploadedFiles)}</p>
<button onClick={uploadFunction}>Upload</button>
<button onClick={() => setUploadedFiles([...defaultValues])}>Reset</button>
</div>
);
};
// Render it
ReactDOM.createRoot(document.getElementById("root"))
.render(<Example title = "Example"/>);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>
Here the result: ["aaa.jpg","bbb.jpg","ccc.jpg","fff.jpg"]
What I want as a result: ["aaa.jpg","bbb.jpg","ccc.jpg","ddd.jpg","eee.jpg","fff.jpg"]
That sounds like a common use case.
- How can I fix this? How should you handle multiple setStates?
- Ideas for a much better way to handle an upload of multiple files? In my use case I have to delete some selected files and upload some selected files on AWS S3 server, if a user clicks on a submit button.
Thanks
Solution
Use the callback version of the setter.
Currently this adds an element to whatever the state array was when the component rendered:
setUploadedFiles([...uploadedFiles, response]);
But if multiple state updates can be batched/queued, then each of them would use that same initial value. Instead, the callback version provides whatever the current state value is in that batch/queue:
setUploadedFiles(prev => [...prev, response]);
Answered By - David
Answer Checked By - - Candace Johnson (ReactFix Volunteer)