How to build a dark mode theme in React Material UI

Suraj Sharma
4 min readAug 16, 2020

--

Created a Context API to switch between light mode and dark mode

Introduction

React Material UI is a material design library built using React js that also supports Typescript.

Material UI has many UI components with working examples that make react developers life eaiser when they start integrating material UI components on their react projects.

One thing that I badly miss in React material UI is the support for the dark mode feature.

If you look at the material UI official documentation website, it has a toggle button to switch between light theme and dark theme.

That inspired me to create a dark mode toggle button on this website. It’s written using React Context API, React Hooks, and Material UI ThemeProvider.

The source code is available on the Github repository, URL is available at the end of the article.

I assume you have some basic knowledge about the material UI theme. Before, we get started with how to built a react context API to change between light mode and dark mode using React Material UI and Typscript

You will require to have Node >= 8.10 and npm >= 5.6 on your machine. To create a project, run:

Create the React Context API


npx create-react-app react-material-ui-dark-mode --template typescript
cd react-material-ui-dark-mode npm start

Go inside the folder and create a file. However, it is not mandatory as material UI already has a default theme.

import { createMuiTheme } from '@material-ui/core/styles';const theme = createMuiTheme({ palette: { type: 'light' } }) export default theme;

This piece of code creates a material UI theme that is available across your application using useTheme() custom react hook, for example see the code below

import { useTheme } from '@material-ui/core/styles';const ReactMaterialComponent = () => {  const defaultTheme = useTheme();  ....  return (
<p>{defaultTheme.palette.type}}</p>
);
}
export default ReactMaterialComponent;

There are many use cases where we might require to access our application’s theme in our react components that I might cover in my future Material UI tutorials.

Second, let’s create a React Context API ThemeDispatchContext.tsx file

Then, define an interface ThemeProviderProps to provide type to the ThemeProvider component.

interface ThemeProviderProps {
children: React.ReactNode
theme: Theme
}

Create a react Context using React.createContext() and a ThemeProvider function component thats returns aThemeDispatchContext.Provider JSX element wrapped inside the Material UI ThemeProvider.

const ThemeDispatchContext = React.createContext<any>(null);const ThemeProvider: React.FC<ThemeProviderProps> = ({
children, theme
}) => {
return (
<MuiThemeProvider theme={memoizedTheme}><ThemeDispatchContext.Provider value={dispatch}>
{children}
</ThemeDispatchContext.Provider>
</MuiThemeProvider>
)}

The idea here is to create a reducer and pass its dispatch to the value of ThemeDispatchContext.Provider so that it accessible across the children, later a toggle button can dispatch an action to toggle the theme.

So, based on the idea, create a reducer and pass it to the useReducer() hook

const themeInitialOptions = { paletteType: 'light' };const [themeOptions, dispatch] = React.useReducer((
state: any, action: any)=> {
switch (action.type) {
case 'changeTheme':
return { ...state, paletteType: action.payload }
default: throw new Error();
}}, themeInitialOptions);

themeOptions state defines the current paletteType.

The piece of code below creates a memoized theme that would create a new theme object every time themeOptions.paletteType changes.

const memoizedTheme = React.useMemo(()=>{
return createMuiTheme({
...theme,
palette: { type: themeOptions.paletteType }});
}, [themeOptions.paletteType]);

Final Code

The final code looks like this

import React from 'react';
import { createMuiTheme, ThemeProvider as MuiThemeProvider, Theme } from '@material-ui/core/styles';
interface ThemeProviderProps { children: React.ReactNode theme: Theme }const ThemeDispatchContext = React.createContext<any>(null);const ThemeProvider: React.FC<ThemeProviderProps> = ({ children, theme }) => { const themeInitialOptions = { paletteType: 'light' } const [themeOptions, dispatch] = React.useReducer( (state: any, action: any)=> { switch (action.type) {
case 'changeTheme':
return { ...state, paletteType: action.payload }
default: throw new Error(); } },
themeInitialOptions);
const memoizedTheme = React.useMemo(()=>{
return createMuiTheme({
...theme,
palette: { type: themeOptions.paletteType }
})
) }, [themeOptions]);
return ( <MuiThemeProvider theme={memoizedTheme}> <ThemeDispatchContext.Provider value={dispatch}>
{children}
</ThemeDispatchContext.Provider>
</MuiThemeProvider> )
}
export default ThemeProvider;

A Bonus Code

As you might have noticed the above ThemeProvider is not reusable in the sense every time you import the ThemeProvider you will be required to import ThemeDispatchContext, for the same reason I have not exported theThemeDispatchContext.

Instead, I am going to a create custom hook useChangeTheme() that does the heavy lifting of using ThemeDispatchContext,
returns a changeTheme() that dispatches a 'changeTheme' action on calling.

export const useChangeTheme = () => { 
const dispatch = React.useContext(ThemeDispatchContext);
const theme = useTheme();
const changeTheme = React.useCallback(()=> dispatch({
type: 'changeTheme',
payload: theme.palette.type === 'light' ? 'dark' : 'light' }), [theme.palette.type, dispatch]);
return changeTheme; }

Finally to use it in your project you have to import ThemeProvider and changeTheme.

Here is a complete code with an example

Thank you for reading the article 🙏

Originally published at https://surajsharma.net.

--

--