Search code examples
reactjsreduxredux-toolkit

Thunk is fulfilled before array map finishes


I have a thunk which fetches data from firestore and maps doc IDs to each doc. For some reason it's fulfilled and returns undefined before the mapping finishes. I can verify this because the log before return statement appears a second or two after the fulfilled reducer logs.

My thunk:

export const fetchNotesByCustomerId = createAsyncThunk(
  'fetchNotesByCustomerId',
  async (custId, { getState, dispatch }) => {
    const customerId = custId;
    if (customerId !== undefined) {
      notesService
        .fetchNotesByCustomerId(customerId)
        .then((snapshot) => {
          if (snapshot.docs.length !== 0) {
            const notes = snapshot.docs.map(
              (doc) =>
                ({
                  ...doc.data(),
                  docId: doc.id
                } as INote)
            );
            console.log('notes inside thunk: ', notes);   // This shows after the fulfilled reducer logs

            return {
              error: false,
              data: notes
            };
          } else
            return {
              error: true,
              message: 'No notes found in firebase',
              data: []
            };
        })
        .catch((error) => {
          return { error: true, message: error.message, data: [] };
        });
    } else return { error: true, message: 'No Customer Id' };
  }
);

I know .map is an async function that returns an array of promises, but when I use await, intellisense notifies me that it makes no difference on the behavior of the function.

So as an alternative, I tried to resolve the array of promises like this, but saw no difference:

.then(async (snapshot) => {
      if (snapshot.docs.length !== 0) {
        const notesPromisesArray = snapshot.docs.map(
          (doc) =>
            ({
              ...doc.data(),
              docId: doc.id
            } as INote)
        );
        await Promise.all(notesPromisesArray).then((notes) => {
          console.log('notes inside thunk: ', notes);

          return {
            error: false,
            data: notes
          };
        });
      } else
        return {
          error: true,
          message: 'No notes found in firebase',
          data: []
        };
    })

How can I get this .map to return before the thunk is fulfilled?


Solution

  • The problem you're facing has nothing to do with Redux Async Thunk but basic JavaScript only.

    In the if condition that you have:

    if (customerId !== undefined) {
            notesService
                .fetchNotesByCustomerId(customerId)
                .then((snapshot) => {
                    if (snapshot.docs.length !== 0) {
                        const notes = snapshot.docs.map(
                            (doc) =>
                                ({
                                    ...doc.data(),
                                    docId: doc.id,
                                } as INote)
                        )
                        console.log('notes inside thunk: ', notes) // This shows after the fulfilled reducer logs
    
                        return {
                            error: false,
                            data: notes,
                        }
                    } else
                        return {
                            error: true,
                            message: 'No notes found in firebase',
                            data: [],
                        }
                })
                .catch((error) => {
                    return { error: true, message: error.message, data: [] }
                })
        } 
    

    You're using noteService.fetchNotesByCustomerId() which is an async function.

    When the execution goes to this block, since JavaScript Event loop will forward the async function to its thread pool, it goes to the next step without even the execution of noteService.fetchNotesByCustomerId() getting over and resolves the thunk without returning anything.

    You can easily resolve this by adding a return statement next to your call:

    export const fetchNotesByCustomerId = createAsyncThunk('fetchNotesByCustomerId', async (custId, { getState, dispatch }) => {
        const customerId = custId
        if (customerId !== undefined) {
            return notesService
                .fetchNotesByCustomerId(customerId)
                .then((snapshot) => {
                    if (snapshot.docs.length !== 0) {
                        const notes = snapshot.docs.map(
                            (doc) =>
                                ({
                                    ...doc.data(),
                                    docId: doc.id,
                                } as INote)
                        )
                        console.log('notes inside thunk: ', notes) // This shows after the fulfilled reducer logs
    
                        return {
                            error: false,
                            data: notes,
                        }
                    } else
                        return {
                            error: true,
                            message: 'No notes found in firebase',
                            data: [],
                        }
                })
                .catch((error) => {
                    return { error: true, message: error.message, data: [] }
                })
        } else return { error: true, message: 'No Customer Id' }
    })