Search code examples
javascriptreactjsreduxredux-toolkitrtk-query

Cannot read properties of undefined (reading 'merge') - React RTK


I am trying to have all the slices in store so my app can access it, I saw similar issues on stackoverflow, they are suggesting to use a unique "reducerPath" name. But when I changed that I got another error caller

Warning: Middleware for RTK-Query API at reducerPath "apifb" has not been added to the store.

One more doubt, in store.js if there are more than 3 slices please suggest me a better way to concat them for middleware.

FacebookSlice

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { api } from "../../config/constants";

export const facebookSlice = createApi({
    reducerPath: 'api',
    baseQuery: fetchBaseQuery({
        baseUrl: api.FACEBOOK_GRAPH_URL,
    }),
    endpoints: (builder) => ({
        getPagePhotos: builder.query({
            query: () => ({
                url: api.FB_GET_PAGE_PHOTOS_URL,
                method: 'GET'
            }),
        })
    })
})
export const {
    useGetPagePhotosQuery
} = facebookSlice

serviceSlice.js

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { api } from "../../config/constants";

export const serviceSlice = createApi({
    reducerPath: 'api',
    baseQuery: fetchBaseQuery({
        baseUrl: api.API_BASE_URL,
    }),
    endpoints: (builder) => ({
        getServices: builder.query({
            query: () => ({
                url: '/',
                method: 'GET'
            }),
        })
    })
})

export const {
    useGetServicesQuery
} = serviceSlice

bookingSlice.js

import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
import { api } from "../../config/constants";

export const bookingSlice = createApi({
    reducerPath: 'api',
    baseQuery: fetchBaseQuery({
        baseUrl: api.API_BASE_URL,
    }),
    endpoints: (builder) => ({
        getBookings: builder.query({
            query: () => ({
                url: '/bookings',
                method: 'GET'
            }),
        }),
        createBooking: builder.mutation({
            query: (booking) => ({
                url: '/bookings/create',
                method: 'POST',
                headers:{},
                body: booking
            }),
        })
    })
})

export const {
    useGetBookingsQuery,
    useCreateBookingMutation
} = bookingSlice

store.js

import { configureStore } from '@reduxjs/toolkit';
import { facebookSlice } from './features/api/facebookSlice'; 
import { bookingSlice } from './features/api/bookingSlice'; 
import { serviceSlice } from './features/api/serviceSlice';

const store = configureStore({
    reducer: {
        [facebookSlice.reducerPath]: facebookSlice.reducer,
        [bookingSlice.reducerPath]: bookingSlice.reducer,
        [serviceSlice.reducerPath]: serviceSlice.reducer
    },
    middleware: (getDefaultMiddleware) =>                 
    getDefaultMiddleware().concat([bookingSlice.middleware, serviceSlice.middleware])
});

export default store;

index.js

import React, { Children } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import 'semantic-ui-css/semantic.min.css'
import App from './App';
import reportWebVitals from './reportWebVitals';
import 'bootstrap/dist/css/bootstrap.min.css';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import Layout from './layout';
import Home from './components/home/home'
import Booking from './components/booking/booking'
import { ApiProvider } from '@reduxjs/toolkit/dist/query/react';
import { serviceSlice } from './features/api/serviceSlice';
import { Hallway } from './components/hallway/hallway';
import { Provider } from 'react-redux';
import  store  from './store';

const router = createBrowserRouter([
    {
        path: '/',
        element: <Layout />,
        children: [
            {
                path: "",
                element: <Home />
            },
            {
                path: "booking",
                element: <Booking />
            },
            {
                path: "hallway",
                element: <Hallway />
            }
        ]
    }
])

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <React.StrictMode>
        <Provider store={store}>
            <RouterProvider router={router} />
        </Provider>
    </React.StrictMode>
);
reportWebVitals();

Solution

  • Issues

    • You have multiple API slices all using the default reducerPath value, e.g. "api" and this creates a conflict.

      See reducerPath.

      The reducerPath is a unique key that your service will be mounted to in your store. If you call createApi more than once in your application, you will need to provide a unique value each time. Defaults to 'api'.

    • You are passing an array to the getDefaultMiddleware function instead of just passing the middlewares directly.

    const store = configureStore({
      reducer: {
        [facebookSlice.reducerPath]: facebookSlice.reducer, // api: facebookSlice.reducer
        [bookingSlice.reducerPath]: bookingSlice.reducer,   // api: bookingSlice.reducer
        [serviceSlice.reducerPath]: serviceSlice.reducer    // api: serviceSlice.reducer
      },
      middleware: (getDefaultMiddleware) =>                 
        getDefaultMiddleware()
          .concat([ // <-- passing array, not middleware
            bookingSlice.middleware,
            serviceSlice.middleware
          ])
    });
    

    Solution

    Give each API slice a unique reducer path value.

    export const facebookSlice = createApi({
      reducerPath: 'facebookApi', // <-- unique among API slices
      baseQuery: fetchBaseQuery({
        baseUrl: api.FACEBOOK_GRAPH_URL,
      }),
      endpoints: (builder) => ({
        getPagePhotos: builder.query({
          query: () => ({
            url: api.FB_GET_PAGE_PHOTOS_URL,
            method: 'GET'
          }),
        })
      })
    });
    
    export const serviceSlice = createApi({
      reducerPath: 'serviceApi', // <-- unique among API slices
      baseQuery: fetchBaseQuery({
        baseUrl: api.API_BASE_URL,
      }),
      endpoints: (builder) => ({
        getServices: builder.query({
          query: () => ({
            url: '/',
            method: 'GET'
          }),
        })
      })
    });
    
    export const bookingSlice = createApi({
      reducerPath: 'bookingApi', // <-- unique among API slices
      baseQuery: fetchBaseQuery({
        baseUrl: api.API_BASE_URL,
      }),
      endpoints: (builder) => ({
        getBookings: builder.query({
          query: () => ({
            url: '/bookings',
            method: 'GET'
          }),
        }),
        createBooking: builder.mutation({
          query: (booking) => ({
            url: '/bookings/create',
            method: 'POST',
            headers: {},
            body: booking
          }),
        })
      })
    });
    

    Pass the middlewares directly, .concat handles "merging" them.

    const store = configureStore({
      reducer: {
        [facebookSlice.reducerPath]: facebookSlice.reducer,
        [bookingSlice.reducerPath]: bookingSlice.reducer,
        [serviceSlice.reducerPath]: serviceSlice.reducer
      },
      middleware: (getDefaultMiddleware) =>                 
        getDefaultMiddleware()
          .concat(
            facebookSlice.middleware,
            bookingSlice.middleware,
            serviceSlice.middleware
          )
    });