React: Theming Components

Andrew Bone - Jul 23 '20 - - Dev Community

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, 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.

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.

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)"
    }
  }
};
Enter fullscreen mode Exit fullscreen mode

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.

// 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;
Enter fullscreen mode Exit fullscreen mode

_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.

import React from "react";
import styled, { ThemeProvider } from "styled-components";
import themeModify from "./theme";
Enter fullscreen mode Exit fullscreen mode

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.

const Label = styled.label`
/* styles go here */
`
Enter fullscreen mode Exit fullscreen mode

As you may have noticed we saved the output of styled to Label and that is now out replacement label element. Like so,

<Label>
  <!-- HTML goes here -->
<Label>
Enter fullscreen mode Exit fullscreen mode

The styles that we write inside styled are like Sass 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

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 */
  }
`
Enter fullscreen mode Exit fullscreen mode

Thank to 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.

${props => props.theme.toggle.active};
Enter fullscreen mode Exit fullscreen mode

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.

<ThemeProvider theme={themeModify(props.theme)}>
  <Label>
    <input
      readOnly={readOnly}
      disabled={disabled}
      defaultChecked={defaultChecked}
      onChange={changeHandler}
      type="checkbox"
    />
    <span />
    {children}
  </Label>
</ThemeProvider>
Enter fullscreen mode Exit fullscreen mode

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.

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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 🦄🦄💕❤️🧡💛💚🤓🧠

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .