Last updated on Oct 15, 2022 by Suraj Sharma
In this tutorial, you are going to learn how you can build a dark mode theme toggle button with React Hooks, TypeScript and Material UI
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.
For this tutorial you will require to have Node >= 8.10 and npm >= 5.6 on your machine. To create a project, run:
npx create-react-app react-material-ui-dark-mode --template typescript
cd react-material-ui-dark-mode
npm start
Go inside the src
folder and create a theme.js
file. However, it is not mandatory as material UI already has a default theme.
import { createTheme } from '@mui/material/styles';
const theme = createTheme({
palette: {
type: 'light'
}
})
export default theme;
This piece of code creates a default material UI theme that is available across your application using useTheme() custom react hook, for example see the code below
import { useTheme } from '@mui/material/styles';
const ReactMaterialComponent = () => {
const defaultTheme = useTheme();
....
return (
<p>{defaultTheme.palette.type}}</p>
);
}
export default ReactMaterialComponent;
There are many use cases where you would be require to access your application's theme inside your react components.
Second, 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 a ThemeDispatchContext.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 it's 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 createTheme({
...theme,
palette: {
type: themeOptions.paletteType
}
});
}, [themeOptions.paletteType]);
The final code looks like this
import React from 'react';
import {
createTheme,
ThemeProvider as MuiThemeProvider,
Theme } from '@mui/material/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 createTheme({
...theme,
palette: {
type: themeOptions.paletteType
}
}))
}, [themeOptions]);
return (
<MuiThemeProvider theme={memoizedTheme}>
<ThemeDispatchContext.Provider value={dispatch}>
{children}
</ThemeDispatchContext.Provider>
</MuiThemeProvider>
)
}
export default ThemeProvider;
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 the ThemeDispatchContext
.
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 the complete code with an example
Related Solutions
Rate this post
Suraj Sharma is the founder of Future Gen AI Services. He holds a B.Tech degree in Computer Science & Engineering from NIT Rourkela.