Reputation: 31
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
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