How to build a dark mode theme in React Material UI
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 typescriptcd 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.