I have been following this tutorial to create a React app with AWS Amplify.
https://aws.amazon.com/getting-started/hands-on/build-react-app-amplify-graphql/module-five/
At some point, the following snippet to retrieve the signed urls of images is present.
async function fetchNotes() {
const apiData = await API.graphql({ query: listNotes });
const notesFromAPI = apiData.data.listNotes.items;
await Promise.all(notesFromAPI.map(async note => {
if (note.image) {
const image = await Storage.get(note.image);
note.image = image;
}
return note;
}))
setNotes(apiData.data.listNotes.items);
}
My question is, how does the React app retrieve the urls? From my understanding the .map
function retrieves a new Javascript array which is not stored in a variable when awaited in the await.Promise.all()
part. The state in setNotes
is updated with the const apiData, so the notes in apiData
should not be able to change inplace.
I would appreciate if anyone can share some light on how the shared snippet works.
CodePudding user response:
await Promise.all(notesFromAPI.map(async note => { if (note.image) { const image = await Storage.get(note.image); note.image = image; } return note; }))
Here you are taking your array of notes, and checking if the property .image
of each note is true
. If it is, it retrieves the note image from the Storage and attaches it to the note.
You need Promise.all()
, since to retrieve the note image you need to make an async call, so .map()
returns an array of Promises
now and before to return your updated notes array you must make sure that all the notes have been updated so all of your Promises
have to be in "Resolved" status.
That's what Promise.all()
does, it returns another Promise
that resolves when all of the promises passed have resolved and returns an array with all the values returned by each one of the promises it waited for ( Your updated notes ).
CodePudding user response:
You are correct when you say that .map
returns a new array. However, the objects inside the new array are not new. They are the original objects in the notesFromAPI
array. This is because JavaScript passes arrays and objects by reference.
This is why you're seeing the note.image
updated in the original array. It's the same note object being operated on.
Here's a quick example highlighting what's happening. As you can see, we're comparing the object received in the .map
to the original object, and we're seeing they are the same object.
let people = [{name: 'Alice', age: 21}, {name: 'Bob', age: 22}];
people.map((person, i) => {
console.log(person === people[i]); // true
})
An easier way to write that snippet, avoiding mutating the original objects, might be something like the following:
async function fetchNotes() {
const { data } = await API.graphql({ query: listNotes });
const notesFromAPI = data.listNotes.items;
// The resolved array is a new array with new objects. Mutating either
// will not mutate the original array or objects.
const notesWithImages = await Promise.all(notesFromAPI.map(async note => {
let image = null
if (note.image) {
image = await Storage.get(note.image);
}
// Make a shallow copy of the note, and add the image to the new object.
// The original objects in `notesFromAPI` are not modified
return { ...note, image };
}))
// Now we can set the notes to the new array, along with the new objects.
setNotes(notesWithImages);
}
If you were to console.log your notesFromAPI
array, you'd see that they remain unchanged.