React context (developing a ThemeProvider)

I’ve been looking into theming a React project and whilst I ultimately intended to use the theming capabilities within material-ui I was interested in how I might write our own.

We’re going to be using React context.

Let’s start by creating a theme.ts file which will include our “base” theme style

export const theme = {
  button: {
    backgroundColor: "white",
    borderColor: "red",
    color: "blue",
    borderWidth: "2px",
    borderStyle: "solid",
    padding: "10px",
    fontSize: "24px",
  }
};

In the above code we created a button property which obviously defines our (rather horrible) default button style. Now we need some way to apply this theme (and any new version of our theme) to our application.

Hence we create a ThemeProvider (ThemeProvider.tsx in my case), which looks like this

import React from "react";
import { theme } from "../theme";

export const ThemeContext = React.createContext(theme);

export function ThemeProvider(props: any) {
  return (
    <ThemeContext.Provider value={props.theme}>
      {props.children}
    </ThemeContext.Provider>
  );
}

In this case, we create a React context, and assign a default value to it (in the shape of our base theme). The ThemeProvider function is then used to wrap our application supplying the theme to all components that wish to use it. So here’s an example of the ThemeProvider in use (in the App.tsx)

import React from 'react';
import { ThemeProvider } from './components/ThemeProvider';
import { Button } from './components/Button';
import { myTheme } from './myTheme';

const App: React.FC = () => {
  return (
    <ThemeProvider theme={myTheme}>
      <div className="App">
        <Button>Themed Button</Button>
      </div>
    </ThemeProvider>
  );
}

export default App;

You’ll notice we’ve also taken this opportunity to supply our overridden theme (with a slightly nicer button style) which is shown below (myTheme.ts)

export const myTheme = {
  button: {
    backgroundColor: "green",
    color: "white",
    borderWidth: "2px",
    borderStyle: "solid",
    padding: "10px",
    fontSize: "24px",
  }
};

As you can see from our App.tsx code, we’re using a Button component, so let’s look at that code now (Button.tsx)

import React from "react";
import { ThemeContext } from "./ThemeProvider";

export const Button = (props: any) => (
  <ThemeContext.Consumer>
    {theme => (
      <button style={theme.button} {...props}>
        {props.children}
      </button>
    )}
  </ThemeContext.Consumer>
);

In this code we consume the React context we created via the ThemeProvider file and apply the style to the button, also passing through any props to the button.

Now if you were to run this application the button will be displayed with our new theme (a nice green background button). If you change the App.tsx theme property to the default “base” theme, then we our default style.