redux-toolkit: Jest: TypeError: Cannot read properties of undefined (reading 'reducerPath')

Got the following error. Screenshot 2022-07-01 at 15 40 23 in test

it('should render form fields', async () => {
    renderWithProviders(<FormBuilder {...props} />);
});
export const renderWithProviders = (element: React.ReactElement) => {
  return renderer.create(<Provider store={store}>{element}</Provider>);
};

FormBuilder:

import React, { useEffect } from 'react';
import { View } from 'react-native';
import { Formik, useFormikContext } from 'formik';

import { Button } from '@/Components/Buttons/Button';
import { Checkbox, CheckboxProps } from '@/Components/Controls/Checkbox';
import { OwnAccountSelector, OwnAccountSelectorProps } from '@/Components/OwnAccountSelector';
import {
  AccountSelectorFieldValues,
  CheckboxFieldValues,
  FormBuilderProps,
  InputFieldValues,
  FieldValues,
  FieldTypes,
  BoxInputFormProps,
  SelectorFiledValues,
  BaseInputFormProps,
  BaseInputSubtypes,
  BoxInputSubtypes,
  MobileOperatorFieldValues,
} from './types';
import {
  MobileOperatorCarousel,
  MobileOperatorCarouselProps,
} from '@/Components/Carousels/MobileOperatorCarousel';
import { SelectPicker } from '@/Components/SelectPicker';
import { MobileOperator } from '@/Types/MobileOperator';
import { useInputFocus } from '@/Hooks/input';
import { BankAndAccountInput, BankAndAccountValue } from '../Inputs/BankAndAccountInput';
import { DatePicker } from '../Inputs/DatePicker';
import { BoxInputForm } from './BoxInputForm';
import { BaseInputForm } from './BaseInputForm';
import { BaseInputProps } from '../Inputs/BaseInputs';
import { SelectPickerProps } from '../SelectPicker/types';
import { useInstitutionColors } from '@/Hooks/institutionColors';

const FormValuesGrabber = ({
  onValuesChange,
}: {
  onValuesChange?: (values: any, submitForm: any) => void;
}) => {
  const { values, submitForm } = useFormikContext<FieldValues>();
  useEffect(() => {
    onValuesChange && onValuesChange(values, submitForm);
  }, [values, onValuesChange, submitForm]);
  return null;
};

export function FormBuilder<ReturnType extends FieldValues>({
  formRef,
  fields,
  initialValues = {} as ReturnType,
  onSubmit,
  onValuesChange,
  validationSchema,
  submitButtonText = 'Submit',
  submitButtonStyle = {},
  canProceed = true,
  hideSubmit = false,
}: FormBuilderProps<ReturnType>) {
  const { focusState, setFieldFocus, resetFieldFocus, resetFocusAll } = useInputFocus(fields);
  const institutionColors = useInstitutionColors();

  return (
    <Formik
      innerRef={formRef}
      initialValues={initialValues}
      validateOnMount
      onSubmit={(values) => {
        resetFocusAll();
        onSubmit(values as ReturnType);
      }}
      validationSchema={validationSchema}
    >
      {({ handleSubmit, setFieldValue, setFieldTouched, values, errors, isValid, touched }) => (
        <>
          <FormValuesGrabber onValuesChange={onValuesChange} />
          {fields.map((field) => {
            if (!field.hidden) {
              const {
                name,
                type,
                subtype,
                fieldProps = {},
                additionalComponent,
                additionalComponentProps = {},
              } = field;
              const AdditionalComponent = additionalComponent;
              let fieldComponent = <></>;

              const commonInputProps = {
                isFocused: focusState[name],
                onFocus: () => setFieldFocus(name),
                onBlur: () => {
                  resetFieldFocus(name);
                  setFieldTouched(name);
                },
                error: touched[name] && errors[name] ? (errors[name] as string) : undefined,
              };

              switch (type) {
                case FieldTypes.BASE_INPUT:
                  fieldComponent = (
                    <BaseInputForm
                      {...commonInputProps}
                      {...(fieldProps as BaseInputFormProps)}
                      subtype={subtype as BaseInputSubtypes}
                      value={(values as InputFieldValues)[name]}
                      onChangeText={(value) => setFieldValue(name, value)}
                    />
                  );
                  break;

                case FieldTypes.BOX_INPUT:
                  fieldComponent = (
                    <BoxInputForm
                      {...(fieldProps as BoxInputFormProps)}
                      {...commonInputProps}
                      subtype={subtype as BoxInputSubtypes}
                      value={(values as InputFieldValues)[name]}
                      onChangeText={(value) => setFieldValue(name, value)}
                    />
                  );
                  break;

                case FieldTypes.CHECKBOX:
                  fieldComponent = (
                    <Checkbox
                      {...(fieldProps as CheckboxProps)}
                      isActive={(values as CheckboxFieldValues)[name]}
                      onPress={() => setFieldValue(name, !values[name])}
                    />
                  );
                  break;

                case FieldTypes.OWN_ACCOUNT_SELECTOR:
                  fieldComponent = (
                    <OwnAccountSelector
                      {...(fieldProps as OwnAccountSelectorProps)}
                      selectedAccount={(values as AccountSelectorFieldValues)[name]}
                      onChangeSelected={(account) => setFieldValue(name, account)}
                    />
                  );
                  break;

                case FieldTypes.SELECTOR:
                  fieldComponent = (
                    <SelectPicker
                      {...(fieldProps as SelectPickerProps<any>)}
                      onPress={() => setFieldTouched(name)}
                      error={commonInputProps.error}
                      selectedValue={(values as SelectorFiledValues)[name]}
                      onChangeSelected={(item: any) => setFieldValue(name, item)}
                    />
                  );
                  break;

                case FieldTypes.DATE:
                  fieldComponent = (
                    <DatePicker
                      {...(fieldProps as BaseInputProps)}
                      error={commonInputProps.error}
                      onClose={() => setFieldTouched(name)}
                      value={(values as InputFieldValues)[name]}
                      onChangeDate={(date) => setFieldValue(name, date)}
                    />
                  );
                  break;

                case FieldTypes.MOBILE_OPERATOR_CAROUSEL:
                  fieldComponent = (
                    <MobileOperatorCarousel
                      {...(fieldProps as MobileOperatorCarouselProps)}
                      value={(values as MobileOperatorFieldValues)[name]}
                      onChangeSelected={(item: MobileOperator) => setFieldValue(name, item)}
                    />
                  );
                  break;

                case FieldTypes.BANK_AND_ACCOUNT:
                  fieldComponent = (
                    <BankAndAccountInput
                      isFocused={commonInputProps.isFocused}
                      onBlur={commonInputProps.onBlur}
                      onFocus={commonInputProps.onFocus}
                      error={commonInputProps.error as {}}
                      value={values[name] as BankAndAccountValue}
                      setFieldValue={(value) => setFieldValue(name, value)}
                    />
                  );
                  break;

                default:
                  break;
              }

              return (
                <View key={name}>
                  {fieldComponent}
                  {AdditionalComponent && (
                    <AdditionalComponent value={values[name]} {...additionalComponentProps} />
                  )}
                </View>
              );
            }
          })}

          {!hideSubmit && (
            <Button
              onPress={handleSubmit}
              disabled={!isValid || !canProceed}
              title={submitButtonText}
              style={submitButtonStyle}
              color={institutionColors.primary}
            />
          )}
        </>
      )}
    </Formik>
  );
}

Store:

import EncryptedStorage from 'react-native-encrypted-storage';
import { combineReducers } from 'redux';
import {
  persistReducer,
  persistStore,
  FLUSH,
  REHYDRATE,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
} from 'redux-persist';
import { configureStore, isRejectedWithValue, Middleware, MiddlewareAPI } from '@reduxjs/toolkit';
import { navigate } from '@/Navigators/Root';
// Services
import { rtkQueryService } from '@/Services/api';
// State
import commonSlice, { addError, clearError } from '@/Store/Common/commonSlice';
import profileSlice from '@/Store/Profile/profileSlice';
import userSlice from '@/Store/User/userSlice';
import catalogsSlice from '@/Store/Catalogs/catalogsSlice';
import startup from '@/Store/Startup';
import i18n from '@/Translations';

const rtkQueryErrorLogger: Middleware = (api: MiddlewareAPI) => (next) => (action) => {
  if (isRejectedWithValue(action)) {
    let errorMessage = i18n.t('errorLogger.defaultError');

    if (action?.payload?.error) {
      errorMessage = action?.payload?.error.replace('Error: ', '').toLowerCase();
      action.payload.error = errorMessage;
    }

    api.dispatch(addError(errorMessage));

    const { common } = api.getState() as RootState;

    if (!common.pendingRequests.length && common.asyncError) {
      navigate('PopUpModal', {
        icon: 'error',
        title: i18n.t('errorLogger.modalTitle'),
        text: common.asyncError,
      });

      api.dispatch(clearError());
    }
  }

  return next(action);
};

const reducers = combineReducers({
  // State
  // When adding new slice add clear slice to clearUserData() except persistConfig
  startup,
  common: commonSlice,
  profile: profileSlice,
  catalogs: catalogsSlice,
  user: userSlice,
  // Services
  // When adding new service add it to services array inside clearRTKQueryCache()
  [rtkQueryService.reducerPath]: rtkQueryService.reducer,
});

const persistConfig = {
  key: 'root',
  storage: EncryptedStorage,
  whitelist: ['profile'],
};

const persistedReducer = persistReducer(persistConfig, reducers);

const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) => {
    const middlewares = getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }).concat([rtkQueryErrorLogger, rtkQueryService.middleware]);

    if (__DEV__ && !process.env.JEST_WORKER_ID) {
      const createDebugger = require('redux-flipper').default;
      middlewares.push(createDebugger());
    }

    return middlewares;
  },
});

const persistor = persistStore(store);

export { store, persistor };

export type RootState = ReturnType<typeof store.getState>;

export type AppDispatch = typeof store.dispatch;

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 2
  • Comments: 17 (4 by maintainers)

Most upvoted comments

Having 0, 1 or one million endpoints is not your problem. Your rtkQueryService is getting imported as undefined.

There are two possible reasons for this:

  • your jest generally deals with imports an export incorrectly
  • you have a circular import that accidentally is resolved correctly when you use your bundler, but not when you use jest. This is the more likely option.

Look for an import circle where the file with rtkQueryService imports from a file that also imports rtkQueryService.

Hello! Here is my createApi()

export const rtkQueryService = createApi({
  tagTypes: ['accountBalance'],
  reducerPath: 'rtkQueryService',
  baseQuery: baseQueryWithInterceptor,
  endpoints: () => ({}),
});

And then I inject endpoints (in the separate files):

export const beneficiaryApi = rtkQueryService.injectEndpoints({
  endpoints: (build) => ({
    getContactUsDetails: build.query<Types.GetContactUsDetailsResponse, number>({
      query: (id) => ({
        url: `${ENDPOINTS.GET_CONTACT_US_DETAILS}/${id}`,
        method: 'GET',
      }),
    }), ...

Hello! I know what is your problem now! 😎 You need to make at least one endpoint under your createApi(), before injecting with injectEndpoints()! In your case, as you only have one endpoint, then you replace it under the createApi(). I know this could be strange, but this is how it works.

So try this:

export const rtkQueryService = createApi({
  tagTypes: ['accountBalance'],
  reducerPath: 'rtkQueryService',
  baseQuery: baseQueryWithInterceptor,
  endpoints: (build) => ({
    getContactUsDetails: build.query<Types.GetContactUsDetailsResponse, number>({
      query: (id) => ({
        url: `${ENDPOINTS.GET_CONTACT_US_DETAILS}/${id}`,
        method: 'GET',
      }),
    })
});

And disable your injectEndpoints().

Having 0, 1 or one million endpoints is not your problem. Your rtkQueryService is getting imported as undefined.

There are two possible reasons for this:

  • your jest generally deals with imports an export incorrectly
  • you have a circular import that accidentally is resolved correctly when you use your bundler, but not when you use jest. This is the more likely option.

Look for an import circle where the file with rtkQueryService imports from a file that also imports rtkQueryService.

Thank you! The issue was resolved!

@arthedza I am having the same issue, how did you solve it ? I have no circular imports