sidebar

How to Configure Theming or Theme Switching with React and TypeScript

I had a project requirement to support theming in react-based applications. So there I have come up with a simple and quick solution to build an application with a pre-defined theme (mainly background color, text color, fonts, etc). Let's check how I have approached solving the problem.

React typescript theme using styled component

Themeing & Theme Swtiching

* leverage styled-componet (npm library) 

* define a theme model (holds the property I want to change - such as background color, text color)

* create multiple theme & apply switch function

* leveraged React Context API to store theme value

What would it look like?

react theme green
react theme light
react theme dark

 

 Above I have three different colors and based on theme value, I can change the background color and text color.

 


Setup React with TypeScript

Let's set up an application with React, TypeScript and styled-component. I will be using create-react-app command to build the application. 

// setup 

npx create-react-app redux-demo-app --template typescript

npm install --save @types/react-router-dom

 

// add botstrap 

npm install --save bootstrap react-bootstrap 

npm install --save node-sass

 

// add styled-components

npm install --save styled-components @types/styled-components

 

 

Also, import @import "~bootstrap/scss/bootstrap"; as well as your scss file. All project is now setup and it will run the application on your web browser. 

What is styled-component & what it is being used?

styled-components: A flexible way to style React components with CSS. It provides out-of-the-box theming support using a wrapper component called, <ThemeProvider>. This component is responsible for providing the theme to all other React components that are wrapped within it.

 


Folder structure and multiple theme file creations

Before implementing a theme file, let's create some folder structure for which I can keep respective files (.tsx, .ts, and .scss) files to configure my codebase. 

react theme code structure

 

Create styled.d.ts file for TypeScript and here we will be defining our theming properties. 

styled.d.ts

 

import 'styled-components';

 

declare module 'styled-components' {

export interface DefaultTheme {

themeType: string;

 

colors: {

background: string,

text: string

};

 

section: {

background: string;

text: string;

}

}

}

 

 

Now, based on the above model, let's create 3 files - green.ts, light.ts and dark.ts.

green.ts

export default {

themeType: 'green',

colors: {

background: '#59ab64',

text: '#fff'

},

 

section: {

background: '#008F7A',

text: "#fff"

}

};

light.ts

export default {

themeType: 'light',

colors: {

background: '#f5f5f5',

text: '#333'

},

 

section: {

background: '#D9F7E7',

text: "#B0A8B9"

}

};

dark.ts

export default {

themeType: 'dark',

colors: {

background: '#4B4453',

text: '#fff'

},

 

section: {

background: 'yellow',

text: "#333"

}

};

 

So, I have 3 different theme models, and based on user choice I will load the theme file accordingly. Also, I have one global style to update the theme value based on the user's choice.

You are all set with theming files, let's implement Context API as well to keep user choice and pass the value where ever is required. 


Create a Context store to hold user choice

I am going to create a context store and reducer for storing and updating the context value accordingly. 

ThemeContext.ts

 

import { createContext } from 'react';

import { DefaultTheme } from 'styled-components';

 

export interface IPlaceThemeProvider {

currentTheme: DefaultTheme;

setNewTheme: (args: DefaultTheme) => void

}

 

export const PlaceThemeContext = createContext<IPlaceThemeProvider>({} as IPlaceThemeProvider);

 

ThemeReducer.ts

 

import { DefaultTheme } from "styled-components";

 

const ThemeReducer = (state: any, updatedTheme: DefaultTheme) => {

return {...state, updatedTheme};

}

 

export default ThemeReducer;

 

So far, I have the following granular stuff, and the only thing remaining is to call some methods accordingly. Let's see what I have so far and what is remain to build -

What I have so far - 

  1. React with TypeScript code has been configured
  2. The theme has been configured
  3. Context store has been setup
  4. imported bootstrap CSS

What is remain?

  1. Need to encapsulate the whole app by context provider and set the default theme to load
  2. Update context store, if the user make some different choice
  3. Optimize code re-rendering method by using memo & useCallback
  4. Demo

 


Integrate context provider & load app with default theme

In App.tsx file, I am going to declaire theme context and initialize with default theme value. 

In header.tsx file, I have an option to chnage the theme, and for that I need to load all three options (green, light & dark) and using useContext hook I can get the current context store value and update accordingly by calling reducer method. 

Header.tsx

 

const themeContext = useContext(PlaceThemeContext);

 

const themeSlection = useCallback((type: string) => {

const selectedTheme: IThemeConfig = themeArray.filter(theme => theme.themeName === type)[0];

themeContext.setNewTheme(selectedTheme.themeValue);

}, []);

 

Render method

<ThemeContainer>

{themeArray.map((theme: IThemeConfig, index: number) => (

<Box type={theme.themeName}

boxColor={theme.themeColor}

onBoxClick={themeSlection}

key={index}></Box>

))}

</ThemeContainer>

 

 

//Box component

 

type Props = {

type: string,

boxColor: string,

onBoxClick: (type: string) => void

}

 

const Box = (props: Props) => {

return (

<>

<CustomContainer className={`${styles["box"]}`}

style={{ backgroundColor: `${props.boxColor}` }}

onClick={() => props.onBoxClick(props.type)}

></CustomContainer>

</>

);

}

 

export default memo(Box);

 

 

Here, in above using memo and useCallback hooks will restrict to reload child component, in case of changing differnet theme. 


Demo:

Please share your comments and happy Coding!

- LP