Home > Enterprise >  how does promise and useState really work in React with AWS Amplify?
how does promise and useState really work in React with AWS Amplify?

Time:04-04

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.

  • Related