-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Hey guys, I hate asking another "undefined is not an object" question related to RTK, but I'm stuck. The error I'm getting is: undefined is not an object (evaluating '_api.gravyApi.injectEndpoints'). I've tried looking for all circular dependencies, but I'm not seeing any. Any help would be greatly appreciated! Thx in advance.
For context, this was a legacy Redux project that we have started integrating RTK into. Libs are:
"react": "18.1.0"
"react-native": "0.70.1" (not using Fabric)
"@reduxjs/toolkit": "^1.8.5"
"react-redux": "^8.0.4"
"typescript": "^4.8.4"
Details
I have a userSlice file that is importing the userApi so that I can add a builder.addMatcher in the extraReducers that listens for userApi.endpoints.getUser.matchFulfilled.
If I remove this extraReducer related code, I don't have any issues, but if I leave it, I get the error (see HERE in code below).
In fact, the same is true if I try to add a matcher for any of my api's other than the main api. Oddly enough, if I were to do something like gravyApi.endpoints.getApplicationSettings.matchFulfilled it would work.
userApi
import {User} from '../../types';
import {getTimeZone} from 'react-native-localize';
import {gravyApi, getValidationStatus} from './api';
type UpdateUserArgs = {
user: Partial<User>;
token: string;
};
export const userApi = gravyApi.injectEndpoints({
endpoints: builder => ({
getUser: builder.query({
query: id => ({
url: `/users/${id}`,
validateStatus: getValidationStatus,
}),
providesTags: ['User'],
}),
updateUser: builder.mutation<User, UpdateUserArgs>({
query: ({user, token}) => {
const zone = getTimeZone();
return {
url: `/users/${user.id}`,
method: 'PUT',
body: {
user: {
...user,
time_zone: zone,
},
},
validateStatus: getValidationStatus,
};
},
invalidatesTags: ['User'],
}),
}),
});
export const {useGetUserQuery, useLazyGetUserQuery, useUpdateUserMutation} =
userApi;userSlice
import {User} from '../../types';
import {userApi} from '../apis/userApi';
import {createSlice, PayloadAction} from '@reduxjs/toolkit';
type SliceState = User | null;
const initialState: SliceState = null;
const slice = createSlice({
name: 'user',
initialState: initialState,
reducers: {
signOut: () => initialState,
setUser(state, action) {
let val = null;
if (action.payload) {
val = {...action.payload};
...
}
return val;
},
setPremiumUser: (state: SliceState, action: PayloadAction<boolean>) => {
if (state) {
state.premium_user = action.payload;
}
},
removeReferringLoanOfficer: (state: SliceState) => {
if (state) {
state.referring_loan_officer = null;
}
},
},
// HERE ...
// Uncommeting breaks the build
// extraReducers: builder => {
// builder.addMatcher(
// userApi.endpoints.getUser.matchFulfilled,
// (state, action) => {
// return action.payload as any;
// },
// );
// },
});
export const {setPremiumUser, removeReferringLoanOfficer, setUser} =
slice.actions;
export default slice.reducer;api
And for context, in case it helps.
import {RootState} from '../store';
import {Platform} from 'react-native';
import Config from 'react-native-config';
import {logOutUser} from '../actions/userActions';
import {createApi, fetchBaseQuery} from '@reduxjs/toolkit/query/react';
import {nativeApplicationVersion, nativeBuildVersion} from 'expo-application';
//#region Types
type ApplicationSettings = {
message?: string;
ios_minimum_build?: string;
android_minimum_build?: string;
savings_interest_rate?: string;
};
type BaseQueryType = ReturnType<typeof fetchBaseQuery>;
//#endregion
//#region Helpers
const getBaseQuery = (): BaseQueryType => {
return fetchBaseQuery({
baseUrl: Config.API,
prepareHeaders: (headers, {getState}) => {
const token = (getState() as RootState).userToken;
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
headers.set('Accept', 'application/json');
headers.set('Content-Type', 'application/json');
...
return headers;
},
});
};
export const getValidationStatus = (
response: Response,
result: any,
): boolean => {
const status = response.status;
if (!result?.isError) {
if (status === 200 || status === 201 || status === 204) {
return true;
}
}
return false;
};
const baseQueryWithReauth: (baseQuery: BaseQueryType) => BaseQueryType =
baseQuery => async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions);
if (result.error && result.error.originalStatus === 401) {
api.dispatch(logOutUser());
}
return result;
};
//#endregion
//#region Core Apis
export const gravyApi = createApi({
reducerPath: 'gravyApi',
tagTypes: ['ApplicationSettings', 'DirectDeposit', 'Courses', 'User'],
baseQuery: baseQueryWithReauth(getBaseQuery()),
endpoints: builder => ({
postNewSession: builder.mutation({
async queryFn(args, queryApi, extraOptions, baseQuery) {
try {
const result = await baseQuery({
url: '/sessions',
method: 'POST',
body: {
user: {
mobile_phone_number: args.phone,
password: args.password,
},
},
});
if (result) {
let mfaAvailable = false;
const status = result.meta?.response?.status;
switch (status) {
...
}
}
throw new Error('Something went wrong.');
} catch (e: any) {
return {error: e.message};
}
},
}),
getApplicationSettings: builder.query<ApplicationSettings, void>({
query: () => ({
url: '/application_settings',
providesTags: ['ApplicationSettings'],
validateStatus: getValidationStatus,
}),
}),
}),
});
//#endregion
// Export hooks for usage in functional components, which are
// auto-generated based on the defined endpoints
export const {useGetApplicationSettingsQuery, usePostNewSessionMutation} =
gravyApi;