RTK Query extraReducers: Uncaught TypeError: Cannot read properties of undefined (reading 'matchFulfilled')

856 Views Asked by At

I am using RTK & RTK Query for state management and data fetching in my app. My store is set up via code-splitting and I get the following error related to my extraReducer defined in storeNew/authSliceNew.js at the initial loading of the app Uncaught TypeError: Cannot read property 'matchFulfilled' of undefined

What I realized so far is that if I comment out my extraReducers in storeNew/authSliceNew.js, re-load the app, and then uncomment them, they seem to be working. But if I have them uncommented right from the start, I get the error. I tried to find a solution for a couple of hours by now, but could not find anything helpful. So I would be glad if anyone could point me to the right direction.

My storeNew/baseApi.js

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

export const baseApi = createApi({
  baseQuery: fetchBaseQuery({ baseUrl: "api/" }),
  endpoints: () => ({}),
  tagTypes: ["User", "Budget"],
});

my storeNew/authApiNew.js

import { baseApi } from "./baseApi";

const authApi = baseApi.injectEndpoints({
  endpoints: (builder) => ({
    registerUser: builder.mutation({
      query: (registerCredentials) => ({
        url: "account/register",
        method: "POST",
        body: { ...registerCredentials },
      }),
    }),
  }),
  overrideExisting: false,
});

export const {
  useRegisterUserMutation,
} = authApi;

my storeNew/authSliceNew.js

import { createSlice } from "@reduxjs/toolkit";
import { baseApi } from "./baseApi";

const initialState = {
  isAuthenticated: false,
  register_success: false,
  feedback: null,
  feedbackType: null,
};

const authSlice = createSlice({
  name: "auth",
  initialState: initialState,
  reducers: {
    resetFeedback: (state) => {
      state.feedback = null;
      state.feedbackType = null;
    },
    resetRegisterSuccess: (state) => {
      state.register_success = false;
    },
  },

  extraReducers: (builder) => {
    builder.addMatcher(
      baseApi.endpoints.registerUser.matchFulfilled,
      (state) => {
        state.register_success = true;
        state.feedback = null;
        state.feedbackType = null;
      }
    );
  },
});

export const authActions = authSlice.actions;

export default authSlice;

my storeNew/index.js

import { configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { baseApi } from "./baseApi";
import authSliceNew from "./authSliceNew";

const store = configureStore({
  reducer: {
    [baseApi.reducerPath]: baseApi.reducer,
    authNew: authSliceNew.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(baseApi.middleware),
});

setupListeners(store.dispatch);

export default store;
2

There are 2 best solutions below

0
phry On BEST ANSWER

If you access baseApi.endpoints.registerUser.matchFulfilled here, you have no guarantee that the registerUser endpoint has already been injected at that point in time (meaning: that the file injecting it has been included yet).

Export authApi instead, and use authApi.endpoints.registerUser.matchFulfilled.

authApi is the same object as baseApi (literally, a reference to the same object!), but at the time authApi is available to you, you have a guarantee that the injectEndpoints call for registerUser has been made.

0
Nipun Ravisara On

if you're using @rtk-query/codegen-openapi to generate your endpoints you need some small configuration tweaks.

Add exportName to your config file. this will export the endpoint injected baseApi instance as the export name you provided.

config.ts

import type { ConfigFile } from "@rtk-query/codegen-openapi";

const config: ConfigFile = {
  schemaFile: "PATH_TO_SCHEMA"
  apiFile: "../utils/clients/baseApi.ts", // base API instance
  apiImport: "baseApi",
  exportName: "clientEndpoints",
  hooks: true,
  outputFiles: {
    "../services/user.ts": {
      filterEndpoints: [/user/i],
    },
    "../services/auth.ts": {
      filterEndpoints: [/auth/i],
    },
    ... //other outputFile patterns
  },
};

export default config;

Generated endpoint will export injectedRtkApi as clientEndpoints

auth.generated.ts

export { injectedRtkApi as clientEndpoints };

Use this exported clientEndpoints inside your extraReducers

authSlice.ts

import { clientEndpoints } from "@/services/auth";

const authSlice = createSlice({
  name: "auth",
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addMatcher(
      clientEndpoints.endpoints.login.matchFulfilled,
      (state, action) => {
        console.log("fulfilled", action);
        const stateCopy = state;
        stateCopy.userId = action?.payload?.user?.id;
        stateCopy.accessToken = action?.payload?.token;
      }
    );
  },
});

ref: rtk-query/usage/code-generation