2

I am trying to incorporate a dark mode on all my pages in a Next JS project, using Material UI for styling and useContext with useReducer hooks to set dark state. I placed both context provider and material theme provider in the _app.js above all the rest of the code so that every page gets the same theme and context. Header component has the switch mode button and is reading state correctly. But the theme doesn't change. It is always as I define it in the initial conditions in useReducer within useContext. Here is the _app.js code:

function MyApp({ Component, pageProps, props }) {
const ctx = useContext(ThemeContext);
const { darkMode, setDarkMode } = ctx;

useEffect(() => {
console.log(darkMode);
}, [darkMode]);

useEffect(() => {
const theme = localStorage.getItem('pfst-theme');
if (theme) {
  if (theme === 'dark') {
    setDarkMode(true);
  } else {
    setDarkMode(false);
  }
} else {
  localStorage.setItem('pfst-theme', 'light');
  setDarkMode(true);
}
}, []);

return (
<ThemeContextProvider>
  <ThemeProvider theme={darkMode ? darkTheme : lightTheme}>
    <CssBaseline>
      <Box id="back-to-top-anchor" />
      <ScrollToColor02 {...props}>
        <AppBar>
          <Header />
        </AppBar>
      </ScrollToColor02>
      <Component {...pageProps} />
      <Footer />
      <ScrollTop {...props}>
        <Fab color="secondary" size="small" aria-label="scroll back to top">
          <KeyboardArrowUpIcon />
        </Fab>
      </ScrollTop>
    </CssBaseline>
  </ThemeProvider>
</ThemeContextProvider>
);
}

On a side note, console log in useEffect in _app.js logs only on first render and not when dark mode state changes.

Here is the context code:

import { createContext, useReducer } from 'react';

export const ThemeContext = createContext({
setDarkMode: (val) => {},
darkMode: false,
});

const reducer = (state, action) => {
switch (action.type) {
case 'SET_THEME':
  return {
    ...state,
    darkMode: action.payload,
  };
default:
  return state;
}
 };
const initialState = {
darkMode: false,
 };
export const ThemeContextProvider = (props) => {
const [state, dispatch] = useReducer(reducer, initialState);

const setDarkMode = (val) => {
dispatch({
  type: 'SET_THEME',
  payload: val,
});
};
 return <ThemeContext.Provider value={{ 
  darkMode: state.darkMode, setDarkMode}}> 
 {props.children}</ThemeContext.Provider>;
 };

Does anyone have any idea why dark mode never changes in the _app, but changes in other components (i.e. header, where the switch mode button label changes depending on dark mode state)?

If there is something more you require, let me know.

juliomalves
  • 42,130
  • 20
  • 150
  • 146
Čedomir Babić
  • 522
  • 4
  • 12
  • 1
    You're using `useContext(ThemeContext)` in `_app` but `MyApp` itself is not wrapped with the `ThemeContext` provider. You should add the logic in the context provider directly, or in a child further down the tree. – juliomalves Mar 19 '22 at 18:07
  • @juliomalves Adding the logic inside index.js, everything works fine, but then i have to add it every time to every page index. – Čedomir Babić Mar 20 '22 at 01:03
  • 1
    If you want it centralised, then move it inside `ThemeContextProvider` directly. – juliomalves Mar 20 '22 at 08:01

1 Answers1

2

With a great help from Julio Malves I solved this problem by wrapping Material UI-s Theme Provider inside my context provider, and then wrapping everything with it inside _app.js. This way, all the Next JS pages get access to Material UI theme you design without wrapping everything inside context in each page.

Here is the context provider code:

export const ThemeContextProvider = (props) => {
    const [state, dispatch] = useReducer(reducer, initialState);

    const setDarkMode = (val) => {
        dispatch({
            type: 'SET_THEME',
            payload: val,
        });
    };
    return (
        <ThemeContext.Provider value={{ darkMode: state.darkMode, setDarkMode }}>
            <ThemeProvider theme={state.darkMode ? darkTheme : lightTheme}>{props.children}</ThemeProvider>
        </ThemeContext.Provider>
    );
};
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
Čedomir Babić
  • 522
  • 4
  • 12