Simon Mayrhofer
Simon Mayrhofer

Reputation: 31

Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object

I'm having a little trouble with ReactJS and Redux-Toolkit

I want to have a global state for a Theme Switcher. So that i can wrap a Material-UI Theme around my whole project in App.tsx. But i always get the error: Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.

My Store is configured like this:

import { combineReducers, configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { TypedUseSelectorHook, useSelector } from 'react-redux';
import { applyMiddleware } from 'redux';
import { themeUiReducer } from './ui/themeSlice';

export const rootReducer = combineReducers({
    data: combineReducers({

    }),
    ui: combineReducers({
        theme: themeUiReducer
    }),
});
export const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) => [...getDefaultMiddleware()],
});

export type RootState = ReturnType<typeof store.getState>
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;

And my themeUIReducer looks like this:

import { createEntityAdapter, createSlice, PayloadAction } from "@reduxjs/toolkit";

export interface IThemes {
    themeMode: boolean | undefined,
    useOs: boolean | undefined
}

const initialState: IThemes = {
    themeMode: false,
    useOs: false
}

const themeSlice = createSlice({
    name: 'themeUI',
    initialState: initialState,
    reducers: {
        setThemeMode: (state, action: PayloadAction<boolean | undefined>) => {
            state.themeMode = action.payload;
        },
        setUseOs: (state, action: PayloadAction<boolean | undefined>) => {
            state.useOs = action.payload;
        },
    },
    extraReducers: {

    }
})

export const { setThemeMode, setUseOs } = themeSlice.actions;
export const themeUiReducer = themeSlice.reducer;

The App.tsx file where i dispatch the reducer is configurered like this:

import React, { useCallback, useState } from 'react';
import './App.css';
import Layout from './components/Layout';
import { Route, Switch, Redirect, withRouter } from 'react-router-dom';
import { ThemeProvider, createTheme, useMediaQuery, CssBaseline } from '@mui/material';
import ThemeSwitcher from './theme/themeSwitcher';
import { useDispatch } from 'react-redux';
import { setThemeMode, setUseOs } from './store/ui/themeSlice';

function App() {

  const dispatch = useDispatch();
  // Get OS-level preference for dark mode
  const prefersDarkMode: boolean | undefined = useMediaQuery("(prefers-color-scheme: dark)");

  const [darkMode, setDarkMode] = useState<boolean | undefined>(prefersDarkMode);
  const themeOption = (b: boolean) => (b ? "dark" : "light");

  const theme = React.useMemo(
    () =>
      createTheme({
        palette: {
          mode: themeOption(darkMode!)
        }
      }),
    [darkMode]
  );

  const toggleDarkMode = (checked: boolean | undefined) => {
    if (checked === null)
      setDarkMode(prefersDarkMode);
    else setDarkMode(checked);
  };

  const dispatchTheme = useCallback(
    (themeMode: boolean | undefined) => {
      if(themeMode === null)
      dispatch(setThemeMode(prefersDarkMode));
      else
      dispatch(setThemeMode(themeMode));
    },
    [dispatch]
  );


  return (
    <ThemeProvider theme={theme}>
      <CssBaseline />
      <ThemeSwitcher useOs={false} themeChanger={dispatchTheme} />
      <Layout>
        <Switch>
          <Route />
          <Route />
          <Route />
        </Switch>
      </Layout>
    </ThemeProvider>
  );
}

export default App;

So i'll use the ThemeSwitcher later in my Navigation/Footer and via global State i'll watch the selection of the user

Upvotes: 1

Views: 567

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42178

Your error message tells you where to look:

Store does not have a valid reducer. Make sure the argument passed to combineReducers is an object whose values are reducers.

The issue here is calling combineReducers with an empty object for your data state.

export const rootReducer = combineReducers({
    data: combineReducers({
        // Here is the problem. It needs a reducer.
    }),
    ui: combineReducers({
        theme: themeUiReducer
    }),
});

Since you aren't using the data property at all right now, the simplest thing is to remove it from your state.

export const rootReducer = combineReducers({
    ui: combineReducers({
        theme: themeUiReducer
    }),
});

If you want to have a placeholder for a future reducer, you can provide a "no-op" reducer function which doesn't do anything.

export const rootReducer = combineReducers({
    data: (state = null, action) => state,
    ui: combineReducers({
        theme: themeUiReducer
    }),
});

Upvotes: 2

Related Questions