Search code examples
reactjsnext.jsreduxredux-toolkitrtk-query

What is the use of extra and extraoptions in Redux-Toolkit Query? How could we populate them when using the query & queryFn?


I am just curious to know the way to populate extra and extraOptions, and from where the arguments must be passed in order so that they can get populated and can be used then in creating some condition based scenario. Well queryFn itself is used for creating custom query function in the Redux-Toolkit (RTK) query (RTKQ).

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";

const shiprocketApiSlice = createApi({
  baseQuery: fetchBaseQuery({
    baseUrl: "https://apiv2.shiprocket.in/v1/external",
    prepareHeaders: (headers, { getState, extra, type, endpoint }) => {
      const token =
        "...someToken";
      token && headers.set("authorization", `Bearer ${token}`);
      console.log(
        "the extra value of the ====> ",
        extra,
        getState(),
        type,
        endpoint,
        headers
      );
      return headers;
    },
  }),
  endpoints: (builder) => ({
    updateUser: builder.mutation({
      query: (user) => {
        return {
          url: `/users/${user.queryArg}`,
          headers: {
            "X-Scope": "1234567890",
          },
        };
      },
    }),
    createOrderShiprocket: builder.mutation({
      queryFn: async (args, api, extraOptions, baseQuery) => {
        console.log(
          "printin all the agrs of the qrfn to see what exactly it is ===> ",
          args,
          api,
          "extraoptions==>",
          extraOptions,
          "base==>",
          baseQuery
        );
        
        const response = await baseQuery(args, api, extraOptions);
        console.log("the response ==>", response);
        return { error: { message: "error occured" } };
      },
    }),
  }),
});

export const { useCreateOrderShiprocketMutation, useUpdateUserMutation } =
  shiprocketApiSlice;

I have used the hooks in this file

"use client";

import React from "react";
import {
  useCreateOrderShiprocketMutation,
  useUpdateUserMutation,
} from "@/store/features/shiprocket/shiprocketApiSlice";

const TestFolderPage = () => {
  const [createOrderShiprocket, { data: TheOrderList }] =
    useCreateOrderShiprocketMutation();

  const [updateUser, { data: updatedData }] = useUpdateUserMutation();
  const handleClick = () => {
    console.log("the click");
    updateUser({ queryArg: 12344 });
    createOrderShiprocket({ userId: 123 });
  };
  return <div onClick={handleClick}>TestFolderPage</div>;
};

export default TestFolderPage;

Solution

  • What is the use of extra and extraOptions in Redux-Toolkit (RTK) query (RTKQ)?

    extra

    Provided as thunk.extraArgument to the configureStore getDefaultMiddleware option.

    BaseQueryApi

    export interface BaseQueryApi {
      signal: AbortSignal
      abort: (reason?: string) => void
      dispatch: ThunkDispatch<any, any, any>
      getState: () => unknown
      extra: unknown             // <-- specified thunk.extraArgument
      endpoint: string
      type: 'query' | 'mutation'
      forced?: boolean
    }
    

    extra is the extra argument used with Redux-Toolkit's Thunk middleware when configuring the Redux store. See Customizing the included Middleware for details. The basic example is a follows:

    import { configureStore } from '@reduxjs/toolkit';
    
    const store = configureStore({
      reducer,
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          thunk: {
            extraArgument: /* whatever value you need here */,
          },
        }),
    });
    

    This extraArgument is exposed out in the thunkApi of createAsyncThunk actions, and apparently as part of the Redux-Toolkit Query's prepareHeaders function signature.

    type prepareHeaders = (
      headers: Headers,
      api: {
        getState: () => unknown
        extra: unknown              // <-- the extra arg from store
        endpoint: string
        type: 'query' | 'mutation'
        forced: boolean | undefined
      }
    ) => Headers | void
    

    extraOptions

    See extraOptions in the docs:

    Quite simply, extraOptions is an optional object that is passed as the third argument to the supplied baseQuery function, e.g. the base query function returned by fetchBaseQuery in your code example. See fetchBaseQuery signature.

    type FetchBaseQuery = (
      args: FetchBaseQueryArgs
    ) => (
      args: string | FetchArgs,
      api: BaseQueryApi,
      extraOptions: ExtraOptions // <-- third arg, the extra options
    ) => FetchBaseQueryResult
    

    How could be populate them when using the query & queryFn?

    I've already covered above where and how to specify/add the extra value when configuring the store. The extraOptions can specified per endpoint.

    Example:

    updateUser: builder.mutation({
      query: (user) => ({
        url: `/users/${user.queryArg}`,
        headers: {
          'X-Scope': '1234567890',
        },
      }),
      extraOptions: {
        // any custom extra options/properties you want to pass to the base query
      },
    }),
    createOrderShiprocket: builder.mutation({
      queryFn: async (args, api, extraOptions, baseQuery) => {     // <-- accessed here
        ...
        
        const response = await baseQuery(args, api, extraOptions); // <-- passed through
    
        ...
      },
      extraOptions: {
        // any custom extra options/properties you want to pass to the base query
      },
    }),
    
    

    This alone isn't particularly useful until you have the need to customize your base query function. See Customizing Queries.

    For example, a fairly trivial customization is to add automatic retries for failed queries.

    import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react';
    
    const baseQuery = retry(
      fetchBaseQuery({
        baseUrl: "https://apiv2.shiprocket.in/v1/external",
        prepareHeaders: (headers, { getState, extra, type, endpoint }) => {
          const token = "...someToken";
    
          token && headers.set("authorization", `Bearer ${token}`);
          
          return headers;
        },
      }),
      {
        maxRetries: 5,
      },
    );
    
    const shiprocketApiSlice = createApi({
      baseQuery, // <-- 5 retries by default
      endpoints: (builder) => ({
        updateUser: builder.mutation({
          query: (user) => ({
            url: `/users/${user.queryArg}`,
            headers: {
              'X-Scope': '1234567890',
            },
          }),
        }),
        createOrderShiprocket: builder.mutation({
          queryFn: async (args, api, extraOptions, baseQuery) => {
            ...
    
            const response = await baseQuery(args, api, extraOptions);
    
            ...
          },
          extraOptions: {
            maxRetries: 8 // <-- override default 5
          },
        }),
      }),
    });
    

    It's really up to you and your app's specific use cases and needs what extra and extraOptions are relevant and/or helpful.