React: Theming Components
Tags: react, javascript, tutorial
Published: 2020-07-23 20:06:24

It's another week and we're going to, once again, be looking at `MaterialSwitch`, hopefully it's not getting boring yet. I'm going to add some themeing, using [styled-components](https://www.npmjs.com/package/styled-components), that allows the app to pull from an object of presets but also make one off changes when they're needed.

Here's what we're going to be making. I've switched over to codesandbox, from jsFiddle, in order to have multiple files and have it closer to a real dev experience.

{% codesandbox eager-babbage-psgbj %}

## Theme file

In the theme file, called `interface/theme.js` we have a couple of functions and the theme object.

The theme object is very simple for this project but it can get more and more complicated as you expand your theme. This is our theme object.

```js
export const theme = {
  toggle: {
    active: "#00897B",
    inactive: "#bdbdbd",
    shadow: "0 0 8px rgba(0, 0, 0, 0.2), 0 0 2px rgba(0, 0, 0, 0.4)"
  },
  general: {
    typography: {
      fontFamily: '"Open Sans", "Arial"'
    },
    timingFunction: {
      easeInOut: "cubic-bezier(0.4, 0, 0.2, 1)"
    }
  }
};
```
There are two functions both are used for modifying the theme and making that process as easy as possible. Only one is exported for use though. Let's take a look.

```js
// loop through all levels of an object and update theme accordingly
function _deepSetObject(newTheme, originalTheme) {
  let combinedTheme = Object.assign({}, originalTheme);
  for (let key of Object.keys(newTheme)) {
    if (typeof newTheme[key] === "object" && !Array.isArray(newTheme[key])) {
      combinedTheme[key] = _deepSetObject(newTheme[key], combinedTheme[key]);
    } else {
      combinedTheme[key] = newTheme[key];
    }
  }
  return combinedTheme;
}

// Function to get full theme with modifications
const themeModify = newTheme => {
  if (!newTheme || typeof newTheme !== "object" || Array.isArray(newTheme))
    return theme;
  return _deepSetObject(newTheme, theme);
};

export default themeModify;
```

### _deepSetObject
This function simply goes through our object and updates the theme accordingly. This way we can send only the parts of the theme object we want to change.

### themeModify 
This function takes a new theme object and uses `_deepSetObject` to generate a theme object to return. If it does not get fed an object to start with it will return the original theme with no modifications.

## Changes to MaterialSwitch
We're going to have to make some changes to `MaterialSwitch` now that we're using styled-components, for instance all our CSS is now in the JS file.

### Imports
Our imports now include `styled` and `ThemeProvider` from `styled-components`, I'll show you how they're used soon, and also our `themeModify`, which we exported from our theme.

```js
import React from "react";
import styled, { ThemeProvider } from "styled-components";
import themeModify from "./theme";
```

### styled
`styled` lets us create a standard `HTMLElement` and attach some CSS to it. Generally it's a good idea to create a wrapper div or, as in our case, a different wrapper element. As we already had a label as our outer most element I used `styled` to remake that.

```js
const Label = styled.label`
/* styles go here */
`
```
As you may have noticed we saved the output of `styled` to `Label` and that is now out replacement label element. Like so,

```html
<Label>
  <!-- HTML goes here -->
<Label>
```

The styles that we write inside `styled` are like [Sass](https://sass-lang.com/) which is quite nice as it allows us to write our CSS in a more modern manor.

Now let's look at how we use items from our theme object in `styled`

```js
const Label = styled.label`
  display: inline-flex;
  font-family: ${props => props.theme.general.typography.fontFamily};
  align-items: center;
  margin: 5px 0;

  & span {
    position: relative;
    cursor: pointer;
    /* rest of styles */
  }
`
```

Thank to [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) we simply have to select the layer of our object. For toggle active colour we'd have a slightly different path but it's the same method.

```js
${props => props.theme.toggle.active};
```

### JSX
Let's look at the `HTML` like part of the component. Not much has changed from the last iteration we've added a `ThemeProvider` element as a wrapper around the whole thing and we've changed our `label` component to our new `styled` versions called `Label`.

```html
<ThemeProvider theme={themeModify(props.theme)}>
  <Label>
    <input
      readOnly={readOnly}
      disabled={disabled}
      defaultChecked={defaultChecked}
      onChange={changeHandler}
      type="checkbox"
    />
    <span />
    {children}
  </Label>
</ThemeProvider>
```
You'll notice our `themeModify` function is now being used, we use it to feed out theme, modified or otherwise, into the `ThemeProvider`.

## Result
That was a lot of code but I think it's worth it in the long run. Let's look at how we can used this code now.

```js
export default function App() {
  const magentaTheme = {
    toggle: {
      active: "#FF00FF",
      inactive: "#bb9cbb"
    }
  };
  return (
    <div className="App">
      <MaterialSwitch>Default theme</MaterialSwitch>
      <MaterialSwitch theme={magentaTheme}>Custom theme</MaterialSwitch>
    </div>
  );
}
```

This is the code for the screen you saw in the demo, at the top of the post. Two elements, one using the default theme and one with a slightly variance. We can attach a theme object with the same structure as our main theme, all the parts missing are filled in, to the theme property.

This is very powerful as a theme object can contain every aspect of your program. Using this method it is even possible to load themes from a database, update a theme based on a local text input and a whole host of other things. It's very exciting.

## Signing off
Thank you for reading I hope you got something out of it, I certainly did. Feel free to leave question, corrections or anything else in the comments down below.

Thanks again 🦄🦄💕❤️🧡💛💚🤓🧠