yung peso
yung peso

Reputation: 1766

How to make theme state be persistent when visiting/reloading/or going back a page on the browser?

How can I get my light/dark theme to be persistently saved as a state when a user reloads, visits a new page, or presses back on a browser, etc... Right now it's very inconsistent.

Here is the index.js where I set up my theme. I wrap it around my components.

index.js

import { ThemeProvider, CssBaseline } from "@material-ui/core";
import { createMuiTheme } from "@material-ui/core";

const MyThemeContext = React.createContext({});

export function useMyThemeContext() {
    return useContext(MyThemeContext);
  }
  
function MyThemeProvider(props) {
  const [isDarkMode, setIsDarkMode] = useState(false);

  const theme = useMemo(
    () =>
      createMuiTheme({
        palette: {
          type: isDarkMode ? 'dark' : 'light',
        },
      }),
    [isDarkMode]
  );

  return (
    <ThemeProvider theme={theme}>
      <MyThemeContext.Provider value={{ isDarkMode, setIsDarkMode }}>
        {props.children}
      </MyThemeContext.Provider>
    </ThemeProvider>
  );
}


const routing = (
    <Router>
        <React.StrictMode>
            <MyThemeProvider>
                <CssBaseline />
                    <Header />
                    <Switch>
                        <Route exact path="/" component={App} />
                        <Route path="/register" component={Register} />
                        <Route path="/login" component={Login} />
                        <Route path="/logout" component={Logout} />
                        <Route path="/dash/:slug" component={Bucket} />
                        <Route path="/create" component={CreateBucket}/>
                    </Switch>
                    <Footer />
            </MyThemeProvider>
        </React.StrictMode>
    </Router>
);

ReactDOM.render(routing, document.getElementById('root'));




I use a switch that activates the state of the theme, it's located in my header. header.js

import React from 'react';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import CssBaseline from '@material-ui/core/CssBaseline';
import { makeStyles } from '@material-ui/core/styles';
import { NavLink } from 'react-router-dom';
import Link from '@material-ui/core/Link';
import Button from '@material-ui/core/Button';
import Switch from '@material-ui/core/Switch';
import { useMyThemeContext } from '../index';


const useStyles = makeStyles((theme) => ({
    appBar: {
        borderBottom: `1px solid ${theme.palette.divider}`,
    },
    link: {
        margin: theme.spacing(1, 1.5),
    },
    toolbarTitle: {
        flexGrow: 1,
    },
}));


function Header() {
    const classes = useStyles();

    const {isDarkMode, setIsDarkMode} = useMyThemeContext();

    return (
        <React.Fragment>
            <CssBaseline />
            <AppBar
                position="static"
                color="default"
                elevation={0}
                className={classes.appBar}
            >
                <Toolbar className={classes.toolbar}>
                    <Switch
                        checked={isDarkMode}
                        onChange={() => setIsDarkMode(!isDarkMode)}
                    />
                </Toolbar>
            </AppBar>
        </React.Fragment>
    );
}

export default Header;

How can I fix the inconsistency with the state not being saved? Thank you for the help!

Upvotes: 1

Views: 1318

Answers (1)

buzatto
buzatto

Reputation: 10382

you would use localStorage to persist state in memory, passing a key name. on app reload your initial state would come from that value stored in localStorage, if it's not set then !! will ensure the value is false rather than null

Also pass to Provider a function that updates state and also set the new value to localStorage. this way you don't need remember to set the value at localStorage every place you consume the setIsDarkMode

// if it's not set in localStorage value is null, then !! will set as false
const initialState = !!JSON.parse(localStorage.getItem('theme'))

function MyThemeProvider(props) {
  const [isDarkMode, setIsDarkMode] = useState(initialState);

  // you pass another function where you persist the value to localStorage
  // given your code you may just create a toggle function where you don't need to pass a value. but you can change it to receive an argument
  const toggleDarkMode = () => {
    setIsDarkMode(themeMode => {
      localStorage.setItem('theme', !themeMode)
      return !themeMode
    })
  }

  const theme = useMemo(
    () =>
      createMuiTheme({
        palette: {
          type: isDarkMode ? 'dark' : 'light',
        },
      }),
    [isDarkMode]
  );

  return (
    <ThemeProvider theme={theme}>
      <MyThemeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
        {props.children}
      </MyThemeContext.Provider>
    </ThemeProvider>
  );
}

and at your Header you extract toggleDarkMode instead:

function Header() {
    const classes = useStyles();

    const {isDarkMode, toggleDarkMode} = useMyThemeContext();

    return (
        <React.Fragment>
            <CssBaseline />
            <AppBar
                position="static"
                color="default"
                elevation={0}
                className={classes.appBar}
            >
                <Toolbar className={classes.toolbar}>
                    <Switch
                        checked={isDarkMode}
                        onChange={toggleDarkMode}
                    />
                </Toolbar>
            </AppBar>
        </React.Fragment>
    );
}

export default Header;

Upvotes: 2

Related Questions