In this article we will be starting from scratch, creating a react app, installing Material UI, and setting up a context to store the state of our theme.

The source for this article can be found here.

To start we will be using React with TypeScript. You can either use npm or yarn.

npx create-react-app my-app — template typescript

Here is the command straight from the React website, to install React.

Okay, now we have React installed. Let’s create a React Context to store the theme state of a user. Not all people like dark mode, so we will default to it being false.

interface ContextProps {

darkMode: boolean;

setDarkMode(darkMode: boolean): void;

} const Context = React.createContext<ContextProps>({

darkMode: false,

setDarkMode: () => {},

});

This will create a React Context in which we can store a boolean variable called darkMode, and a setter function called setDarkMode, which can switch between a light and dark mode.

Now we will create a React Context Provider, which we can use anywhere in our app.

interface Props {

children?: React.ReactNode;

} const Provider: React.FC<Props> = ({ children }) => {

const [darkMode, setDarkMode] = useLocalStorage('darkMode', false);

return (

<Context.Provider

value={{

darkMode,

setDarkMode,

}}

>

{children}

</Context.Provider>

);

};

This snippet will create a React Context Provider that will use a custom hook called useLocalStorage.

Check out my article on how to create your own customs hooks, especially the useLocalStorage hook.

Now let’s start exporting our awesome context, so we can use them within our app.

export const useStore = () => useContext(Context); export function withProvider(Component: any) {

return function WrapperComponent(props: any) {

return (

<Provider>

<Component {...props} />

</Provider>

);

};

} export { Context, Provider };

This will export the Context and the Provider to use within our app. But let’s also export some helper functions so we can neatly wrap or components.

The useStore function is our hook into this Context. We will be using this in our component that will switch the theme.

The withProvider function is our wrapper that will enable us to neatly wrap our app component in this context. If you’re not familiar with React Context, here is a link to get caught up.

https://reactjs.org/docs/hooks-reference.html#usecontext

Let’s add a few more helper functions.

export const useApp = () => {

const { darkMode, setDarkMode } = useStore();

return {

darkMode,

setDarkMode,

};

}; export function withThemeProvider(Component: any) {

const WrapperComponent = ({ props }: any) => {

const { darkMode } = useApp();

const theme = darkMode ? darkTheme : defaultTheme;

return (

<ThemeProvider theme={theme}>

<CssBaseline />

<Component {...props} />

</ThemeProvider>

);

};

return withProvider(WrapperComponent);

}

The useApp function just renames the useStore function to something more understandable. This is our App context. Anytime the theme changes it needs to update the entire app. Of course you do not want to do this state change very often. You probably want to useMemo and useCallback to make you app more efficient, but that is a broader topic that we will address in a later article.

The withThemeProvider function is a wrapper that will enable us to wrap our app in a Material UI theme. You can see that I am using the useApp hook and pulling the darkMode boolean to decide which theme to use for Material UI. When that variable state changes it will render this component with the new theme.

Now let’s look at our simple App component.

import React from 'react';

import { makeStyles } from '@material-ui/core/styles';

import { withThemeProvider } from './store/Store';

import Grid from '@material-ui/core/Grid';

import Paper from '@material-ui/core/Paper';

import Box from '@material-ui/core/Box';

import FormControlLabel from '@material-ui/core/FormControlLabel';

import Switch from '@material-ui/core/Switch';

import { useStore } from './store/Store'; const useStyles = makeStyles(theme => ({

root: {

width: '100%',

height: '100%',

},

})); const App: React.FC = () => {

const classes = useStyles();

const { darkMode, setDarkMode } = useStore();

return (

<Grid

className={classes.root}

container

justify="center"

alignItems="center"

>

<Paper>

<Box p={10}>

<FormControlLabel

control={

<Switch

checked={darkMode}

onChange={() => setDarkMode(!darkMode)}

/>

}

label="Dark Mode"

/>

</Box>

</Paper>

</Grid>

);

}; export default withThemeProvider(App);

This is our App component that will take in our useStore hook and grab the darkMode and setDarkMode from the context.

It will render a switch component the user can use to switch on or off the dark mode theme.

You probably want to have this switch in a drawer or a user settings page. Once you have the Context, we wrap the app in our withThemeProvider.

Once again, you should keep in mind, when creating a global context like this, the performance of your app. A global context should only store information about the whole app, such as the theme or the logged in user.

Thanks!