Is there a way to ensure DRY principles are followed while utilizing Redux Toolkit's asyncThunkCreator?

I have a query related to RTK. I find myself repeating code in my action creators created with createAsyncThunk because I want to be able to cancel any request made.

I am thinking of creating a wrapper to streamline this process, but I am facing challenges with TypeScript implementation. Are the arguments used in createAsyncThunk accessible somewhere?

Upon examining the code, I noticed that the thunkAPI (which I am particularly interested in) is defined with GetThunkAPI<'3rd parameter'> using 3 TypeScript parameters.

An example of an action creator could resemble the following:

export const resendValidationKey = createAsyncThunk<
  void,
  IAdminResendValidationKey,
  { rejectValue: AxiosError }
>('admin/resendValidationKey', async (data, thunkAPI) => {
  const { signal, rejectWithValue } = thunkAPI;
  try {
    const source = axios.CancelToken.source();
    signal.addEventListener('abort', () => {
      source.cancel();
    });
    await const response = axios.post(`admin/account/validate/resend`, data, {
      cancelToken: source.token,
    });
    return response.data;
  } catch (error) {
    return rejectWithValue(error);
  }
});

Ideally, I would like a wrapper where I can simply input the URL, method, data, and success callback (if applicable). Is such a solution feasible?

I hope all of this makes sense.

Answer №1

For the purpose of handling custom errors thrown by functions within Redux thunks, I devised a workaround by creating a wrapper around the createAsyncThunk method:

import type { AsyncThunkPayloadCreator } from '@reduxjs/toolkit';
import { createAsyncThunk } from '@reduxjs/toolkit';
//...

const handleReduxResult = async <T>(
    actionName: string,
    fn: () => T | Promise<T>,
    rejectWithValue: (value: unknown) => unknown,
) => {
    try {
        return await fn();
    } catch (e: unknown) {
        const wrappedError = wrapErrorTyped(e);
        wrappedError.actionName = actionName;
        const wrappedSerializedError = createWrappedSerializedError(wrappedError);
        return rejectWithValue(wrappedSerializedError) as T;
    }
};

export interface CustomAsyncThunkConfig {
    [prop: string]: unknown;
}

export const createCustomAsyncThunk = <Returned, ThunkArg = void>(
    name: string,
    thunk: AsyncThunkPayloadCreator<Returned, ThunkArg, CustomAsyncThunkConfig>,
) =>
    createAsyncThunk<Returned, ThunkArg, CustomAsyncThunkConfig>(
        name,
        async (params, thunkApi) =>
            await handleReduxResult(
                name,
                async () => thunk(params, thunkApi),
                thunkApi.rejectWithValue,
            ),
    );

In our case, it was necessary to prevent Redux toolkit's error handling mechanism from interfering with our own error processing workflow.

To avoid excessively repetitive code calls to rejectWithValue, I designed the handleReduxResult function to manage custom error handling and serialization in a more concise manner.

This approach allowed us to inject additional context into the error logs by including an actionName property for better traceability.

Although the implementation may seem intricate due to the complexity of internal types used by Redux toolkit, adapting the handleReduxResult function according to specific requirements is feasible.

const handleReduxResult = async <T>(
    actionName: string,
    fn: () => T | Promise<T>,
    rejectWithValue: (value: unknown) => unknown,
) => {
    return await fn();
};

By modifying this function based on individual use cases, a tailored solution for error handling can be achieved.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best way to refresh the redux toolkit store after logging out from the

After logging out, I've been struggling to reset all reducers in my store using redux toolkit. None of my methods have worked so far. Can someone please provide guidance on how to achieve this? ...

What are some solutions to the error message "Error: Cannot find any matching routes" that appears when trying to switch between tabs following a successful login?

I am facing an issue with my Ionic 4 (4.10.2 with Angular 7.3.1) app where I want to make it accessible only after login. Following a tutorial from , I encountered a problem when trying to access the ion-tabs section of my app. Chrome keeps showing this er ...

Error encountered during the production build of Angular 2. No issues reported during development phase

During development, I can successfully run the code below. However, when I deploy to production, I encounter the following errors: node_modules/@angular/common/src/pipes/async_pipe.d.ts(39,38): error TS2304: Cannot find name 'Promise'. node_modu ...

Launch the Image-Infused Modal

I am completely new to the world of Ionic development. Currently, I am working on a simple Ionic application that comprises a list of users with their respective usernames and images stored in an array. Typescript: users = [ { "name": "First ...

Transform the JSON object into a TypeScript array

Currently working on a project that requires converting a JSON object into a TypeScript array. The structure of the JSON is as follows: { "uiMessages" : { "ui.downtime.search.title" : "Search Message", "ui.user.editroles.sodviolation.entries" : ...

Issue encountered while developing custom Vuejs + Typescript plugin

In my index.ts and service plugin files, I have this structure: service.ts declare interface Params { title: string; description?: string; type?: string; duration?: number; } export default class ServiceToast { public toastRef: any; // componen ...

Omit functions from category

This question reminds me of another question I came across, but it's not quite the same and I'm still struggling to figure it out. Basically, I need to duplicate a data structure but remove all the methods from it. interface XYZ { x: number; ...

In TypeScript, an interface property necessitates another property to be valid

In the scenario where foo is false, how can I designate keys: a, b, c, bar as having an undefined/null/optional type? Put simply, I require these properties to be classified as mandatory only when foo is true. interface ObjectType { foo: boolean; a: nu ...

What is the best way to recycle a single modal in Ionic?

Apologies for the vague title, but I'm facing an issue with creating a single modal to display data from multiple clickable elements, rather than having separate modals for each element. For example, when I click on item 1, its data should be shown in ...

Prisma: Utilizing the include option will retrieve exclusively the subobject fields

I created a function to filter the table building and optionally pass a Prisma.BuildingInclude object to return subobjects. async describeEntity(filter: Filter, include?: Prisma.BuildingInclude): Promise<CCResponse> { try { const entity = await ...

I thought enabling CORS would solve the issue, but it seems like the restrictions

This is my setup for an asp.net core web API: public void ConfigureServices(IServiceCollection services) { services.AddCors(o => o.AddPolicy("CorsPolicy", builder => { builder ...

Changing an array into an object in JavaScript without rearranging the keys

I have a collection { 1: {id: 1, first: 1, last: 5} 2: {id: 2, first: 6, last: 10} 3: {id: 3, first: 11, last: 15} } My goal is to reverse the order of items without rearranging the keys so that it looks like this: { 1: {id: 3, first: 11, last: 15} 2: { ...

What's the most effective method for transferring data to different components?

How can I efficiently pass a user info object to all low-level components, even if they are grandchildren? Would using @input work or is there another method to achieve this? Here is the code for my root component: constructor(private _state: GlobalSta ...

What is the method for implementing an Inset FAB with Material UI in a React project?

Currently, I am working on a project that requires an "Inset Fab" button to be placed between containers. After referencing the Material Design documentation, I discovered that the component is officially named "Inset FAB". While I was able to find some tu ...

"Users have reported that the file upload preview feature in Angular 6 only works after the

I am currently utilizing Angular 6. In my application, I have a simple input type="file" field that passes data to an image source which displays the image I intend to upload. The issue I am facing is that when I select an image for the first time, nothi ...

Using Vue.js 2 on multiple HTML pages with Typescript and ASP.Net Core

My ASP.Net Core MVC project utilizes VueJs2 for more complex tasks, with each view having its own corresponding js file. The directory structure is as follows: ├ Controllers\HomeController.cs (with actions Index & Details) ├ Scripts\Hom ...

Assigning to a constrained type with an indexable signature results in failure

When using typescript 4.7.2, I encountered an issue where the following code fails only when assigning a value: type IndexableByString = { [k: string]: any }; function test<T extends IndexableByString>(target: T, key: string) { var prop = target ...

Tips for making a property non-nullable in Typescript

The Node built-in IncomingMessage type from DefinitelyTyped's definition (used as req in the (req, res, next) arguments) has specified that url can be nullable. This excerpt shows part of the definition: // @types/node/index.d.ts declare module "http ...

Is the variable empty outside of the subscribe block after it's been assigned?

Why is a variable assigned inside subscribe empty outside subscribe? I understand that subscribe is asynchronous, but I'm not sure how to use await to render this variable. Can someone please help me and provide an explanation? I am attempting to retr ...

"Discover the power of Next.js by utilizing dynamic routes from a curated list

I am working on a Next.js application that has a specific pages structure. My goal is to add a prefix to all routes, with the condition that the prefix must be either 'A', 'B', or 'C'. If any other prefix is used, it should re ...