James Ugbanu
James Ugbanu

James Ugbanu

Theme Switching in react and material UI

Theme Switching in react and material UI

James Ugbanu's photo
James Ugbanu

Published on Dec 1, 2020

7 min read

I received a request to write on light/dark mode theme in a React app. Currently, switching theme is such a feature that most apps now have, and it's exciting using this feature, as some (including me) prefer dark mode rather than the light mode. Although I know several others also prefer light mode. Therefore, theming is very important to satisfy users with their preference.

In this tutorial, I will be using some library to achieve this but before that, We should bootstrap our app;

npx create-react-app my-app
cd my-app
npm start

Read more here If you can't get it started.

We can now proceed to the project structure. we will install material UI which is the UI library and react feather for icons. Before that, We will create four (4) files;

Home.js Theme.js TopNav.js constants.js

0_oL3a971asm8bRTz0.png

Your src folder should look like the above. To install material UI and react feather - 

npm install @material-ui/core react-feather - save

I am not going to use any state management library such as redux or context API because I want you to understand the fundamentals and then apply it to your own project with any state management of your choice. In addition, I will not store state value in localstroage, therefore, we will lose the last settings on refresh.

Clear out App.js, on TopNav.js, theme.js and constants.js paste the code below;

Constants.js
export const THEMES = {
LIGHT: 'LIGHT',
DARK: 'DARK'
};

Constants.js holds the dark and light mode values. It helps us to easily change Theme values in one place and also avoid typo errors when repeated on different components.

Theme.js

import { colors, createMuiTheme } from '@material-ui/core';
import { THEMES } from './constants';
const themesOptions = [
{
name: THEMES.LIGHT,
palette: {
type: 'light',
background: {
default:'#008FE6',
dark: '#f4f6f8',
paper: colors.common.white
},
primary: {
main: '#008FE6'
},
secondary: {
main: '#0000'
},
text: {
primary: colors.blueGrey[900],
secondary: colors.blueGrey[600]
}
}
},
{
name: THEMES.DARK,
palette: {
type: 'dark',
background: {
default: '#282C34',
dark: '#1c2025',
paper: '#282C34'
},
primary: {
main: '#3EA7E5'
},
secondary: {
main: '#00609A'
},
text: {
primary: '#e6e5e8',
secondary: '#adb0bb'
}
}
}
];
export const createTheme = (config = {}) => {
const themeOptions = themesOptions.find((theme) => theme.name === config.theme);
const theme = createMuiTheme(themeOptions);
return theme;
};

In theme.js we use createMuiTheme which is a function from material UI to create the app theme. Although, we passed themeOptions as a parameter which contains the right parameter for the selected theme. Don't worry I will explain that in App.js

TopNav.js

import React from 'react';
import {
AppBar,
Box,
Hidden,
IconButton,
Toolbar,
Tooltip,
makeStyles,
} from '@material-ui/core';
import { Menu as MenuIcon, Sun as LightIcon, Moon as DarkIcon } from 'react-feather';
import { THEMES } from './constants';
const useStyles = makeStyles((theme) => ({
root: {
zIndex: theme.zIndex.drawer + 100,
...theme.name === THEMES.LIGHT ? {
boxShadow: 'none',
backgroundColor: theme.palette.primary.main
} : {},
...theme.name === THEMES.DARK ? {
backgroundColor: theme.palette.background.default
} : {},
color: '#fff'
},
toolbar: {
minHeight: 75,
},
}));
const TopNav = ({ toggle, themeState }) => {
const classes = useStyles();
return (
<AppBar className={classes.root}>
<Toolbar className={classes.toolbar}>
<Hidden lgUp>s
<IconButton color="inherit">
<MenuIcon />
</IconButton>
</Hidden>
<Box ml={2} flexGrow={1} />
<Box ml={2}>
<Tooltip title={themeState.mode !== 'LIGHT' ? "Light mode" : "Dark mode"}>
<IconButton
color="inherit"
onClick={toggle}
>
{themeState.mode === 'LIGHT' ? <DarkIcon /> : <LightIcon />}</IconButton>
</Tooltip>
</Box>
</Toolbar>
</AppBar>
);
};
export default TopNav;

We performed a conditional check in makeStyles. It checks the theme colour with a constant colour then sets the appropriate background colour. I will explain the props value in App.js.

Add this code below to Home.js

Home.js

import React from 'react';
import { makeStyles } from '@material-ui/core';
const useStyles = makeStyles((theme) => ({
root: {
backgroundColor: theme.palette.background.dark,
display: 'flex',
height: '100vh',
overflow: 'hidden',
width: '100%'
},
}));
const Home = ({className, ...rest}) => {
const classes = useStyles();
return (
<div className={classes.root}>
</div>
);
};
export default Home;

Let's import all the component we have created to App.js

App.js

import React, { useState } from 'react';
import { ThemeProvider } from '@material-ui/core';
import TopNav from './TopNav';
import { createTheme } from './Theme';
import Home from './Home';
import { THEMES } from './constants';
const App = () => {
const [themeState, setThemeState] = useState({
mode: 'LIGHT'
});
const toggle = () => {
const mode = themeState.mode === THEMES.LIGHT ? THEMES.DARK : THEMES.LIGHT;
setThemeState({ ...themeState, mode: mode });
};
const theme = createTheme({ theme: themeState.mode });
return (
<ThemeProvider theme={theme}>
<TopNav toggle={toggle} themeState={themeState}/>
<Home />
</ThemeProvider>
);
};
export default App;

Firstly, ThemeProvider is a function from material UI that we use to wrap our colour options so it can be passed all through the app. We create a toggle function to switch between states and then pass it to the TopNav component as props. We use react hooks useState to store the current theme mode.

That's it!!! You can now toggle between each theme mode.

theme switch react.gif

Full code here - github.com/JamesUgbanu/react-theme-change

 
Share this