Search code examples
reactjsreduxthisredux-thunkredux-toolkit

Create async thunk loosing context if returned from another functions


I want to create createAsyncThunk function wrapper, because want to refactor 3 big thunks in one with parameters. Here my solution.

Code

export const getExportThunk = <T>(
  handler: (args: T) => Promise<boolean>,
  route: string,
  successText: string
) =>
  createAsyncThunk<boolean, T>(
    route,
    async (args, {rejectWithValue, dispatch}) => {
      try {
        const res = await handler(args);
        return true;
      } catch (e: unknown) {
        return rejectWithValue('unknown');
      }
    }
  );

Usage.

export const exportWorkload = getExportThunk<IServerGetWorkloadBody>(
  api.exportWorkload,
  'ytd/exportWorkload',
  'YTD Workload was successfully downloaded!'
);

But when I'm trying to dispatch the result of getExportThunk, I catch an error that says: this is undefined. How to pass context in this case and moreover which context it should be?

Error stacktrace: stacktrace

Place where redux promise function fails

const promise = (async function () {
        let finalAction: ReturnType<typeof fulfilled | typeof rejected>
        try {
          let conditionResult = options?.condition?.(arg, { getState, extra })
          if (isThenable(conditionResult)) {
            conditionResult = await conditionResult
          }
          if (conditionResult === false) {
            // eslint-disable-next-line no-throw-literal
            throw {
              name: 'ConditionError',
              message: 'Aborted due to condition callback returning false.',
            }
          }
          started = true
          dispatch(
            pending(
              requestId,
              arg,
              options?.getPendingMeta?.({ requestId, arg }, { getState, extra })
            )
          )
//fails in this asignment
          finalAction = await Promise.race([
            abortedPromise,
            Promise.resolve(
              payloadCreator(arg, {
                dispatch,
                getState,
                extra,
                requestId,
                signal: abortController.signal,
                rejectWithValue: ((
                  value: RejectedValue,
                  meta?: RejectedMeta
                ) => {
                  return new RejectWithValue(value, meta)
                }) as any,
                fulfillWithValue: ((value: unknown, meta?: FulfilledMeta) => {
                  return new FulfillWithMeta(value, meta)
                }) as any,
              })
            ).then((result) => {
              if (result instanceof RejectWithValue) {
                throw result
              }
              if (result instanceof FulfillWithMeta) {
                return fulfilled(result.payload, requestId, arg, result.meta)
              }
              return fulfilled(result as any, requestId, arg)
            }),
          ])
        } catch (err) {
          finalAction =
            err instanceof RejectWithValue
              ? rejected(null, requestId, arg, err.payload, err.meta)
              : rejected(err as any, requestId, arg)
        }

Solution

  • As @markerikson said, the problem was with context of api.exportWorkload, it was loosing its context after firing it as handler() and was trying to get access to it. The solution.

    export const exportWorkload = getExportThunk<IServerGetWorkloadBody>(
      api.exportWorkload.bind(api),
      'ytd/exportWorkload',
      'YTD Workload was successfully downloaded!'
    );
    
    

    Or use arrow function instead default function

      public exportWorkload = async (body: IServerGetWorkloadBody) => {
        const res = await this.ytdService.exportWorkload(body);
    
        return res;
      };