skip to Main Content

I’ve been using Redux for few time and i’m struggling with a Thunk answer…

In my slice, I’m calling an async function "handleBoxDeliveryAsync"

extraReducers: (builder) => {
    builder
        .addCase(handleBoxDeliveryAsync.fulfilled, (state, action) => {
                if (state.scanMode === "remove") {
                  return;
                }
                const payload = action?.payload?.payload as Data;
                const box = payload.data.resource;
                //If box hasn't COLLECTED status
                if (box.status !== "COLLECTED") {
                  toast.warn("Le bac de collecte n'est pas disponible à la livraison");
                  return;
                }
                //If box hasn't been scanned yet
                if (!state.boxes.find((b) => b.external_id === box.external_id)) {
                  state.boxes.push({ external_id: box.external_id, weight: 0 });
                  toast.success("Le bac a été ajouté");
                  return;
                }
                //If box has already been scanned
                toast.warn("Le bac a déjà été scanné");
              })
...

this Thunk is responsible to play different functions depending on the "scanMode" (QRCode Reader)

export const handleBoxDeliveryAsync = createAppAsyncThunk(
  "collectDelivery/handleBox",
  async (code: string, store) => {
    try {
      if (store.getState().deliveryCollect.scanMode === "add") {
        return store.dispatch(getDeliveryBoxAsync(code));
      }
      if (store.getState().deliveryCollect.scanMode === "remove") {
        return store.dispatch(removeDeliveryBoxAsync(code));
      }
    } catch (e) {
        return Promise.reject(e); // Explicitly reject the promise if an error occurs.
    }
  }
);

export const getDeliveryBoxAsync = createAppAsyncThunk(
  "collectDelivery/getBox",
  async (code: string) => {
    try {
      return await getBox(code);
    } catch (e) {
      return Promise.reject(e);
    }
  }
);

export const removeDeliveryBoxAsync = createAppAsyncThunk(
  "collectDelivery/removeBox",
  async (code: string, thunk) => {
    if (
      thunk
        .getState()
        .deliveryCollect.boxes.map((b) => b.external_id)
        .includes(code)
    ) {
      return Promise.resolve(code);
    }
    return Promise.reject(code);
  }
);

My problem is that when "getDeliveryBoxAsync" is rejected, "handleBoxDeliveryAsync" is still considered as "fulfilled". Any idea why?
Thanks a lot

I tried to handle error and Promises

2

Answers


  1. Chosen as BEST ANSWER

    I found a solution! I moved my logic in to the "getDeliveryBoxAsync"

    .addCase(getDeliveryBoxAsync.fulfilled, (state, { payload }) => {
            const box = payload.data.resource;
            if (box.status !== "COLLECTED") {
              toast.warn("Le bac de collecte n'est pas disponible à la livraison");
              return;
            }
            //If box hasn't been scanned yet
            if (!state.boxes.find((b) => b.external_id === box.external_id)) {
              state.boxes.push({ external_id: box.external_id, weight: 0 });
              toast.success("Le bac a été ajouté");
              return;
            }
            //If box has already been scanned
            toast.warn("Le bac a déjà été scanné");
          })
          .addCase(getDeliveryBoxAsync.rejected, (_, { error }) => {
            if (error.code === "ERR_BAD_REQUEST") {
              toast.warn("Le bac n'existe pas");
              return;
            }
            toast.warn("une erreur est survenue");
          })


  2. Issue

    The handleBoxDeliveryAsync is synchronously returning, e.g. resolving, the result of dispatching either of the getDeliveryBoxAsync or removeDeliveryBoxAsync actions, which both do their own asynchronous work. By the time either of them encounter any issues and throw/reject, it’s too late, handleBoxDeliveryAsync resolved with a value that is another Promise.

    Solution

    Seems you would like to wait for the result of either getDeliveryBoxAsync or removeDeliveryBoxAsync actions to resolve prior to allowing handleBoxDeliveryAsync to settle. You also don’t need to explicitly return Promise.resolve/Promise.reject when using createAsyncThunk actions, Redux-Toolkit handles this for you.

    For more details see Handling Thunk Results.

    export const getDeliveryBoxAsync = createAppAsyncThunk(
      "collectDelivery/getBox",
      // thrown/rejected will be caught/handled in handleBoxDeliveryAsync
      (code: string) => getBox(code),
    );
    
    export const removeDeliveryBoxAsync = createAppAsyncThunk(
      "collectDelivery/removeBox",
      async (code: string, thunkApi) => {
        const { deliveryCollect: { boxes } } = thunkApi.getState();
    
        if (boxes.map((b) => b.external_id).includes(code)) {
          return code; // <-- fulfilled
        }
        return thunkApi.rejectWithValue(code); // <-- rejected
      }
    );
    
    export const handleBoxDeliveryAsync = createAppAsyncThunk(
      "collectDelivery/handleBox",
      async (code: string, thunkApi) => {
        const { deliveryCollect: { scanMode } } = thunkApi.getState();
    
        try {
          if (scanMode === "add") {
            // await & return the unwrapped result
            return await thunkApi.dispatch(getDeliveryBoxAsync(code)).unwrap();
          }
          if (scanMode === "remove") {
            // await & return the unwrapped result
            return await thunkApi.dispatch(removeDeliveryBoxAsync(code)).unwrap();
          }
        } catch (e) {
          // There was a thrown error or rejected Promise, will reject
          return thunkApi.rejectWithValue(e);
        }
      }
    );
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search