Is this even a developer blog without a dark mode?!

Using the Context API and then React hooks to implement a dark mode

July 11, 2020

Implementing a dark mode shot to the top of my todo list as it’s a great opportunity to get a bit more familiar with the React Context API (I’ve already had some introduction to it via work and a lovely egghead.io course). I am also very much a dark mode user myself, so let’s go!

A dark mode requires large scale changes in style which encompass fonts, backgrounds, borders, SVGs, etc. In a React app, this can be achieved using the Context API to enable a single toggle to control the style globally.

Using Context for implementing dark mode

Everyone’s first port of call for looking into Context should be the React docs. In a nutshell, the Context is set at a very high level in the component tree and makes the data within the Context accessible to all the components in the tree without having to pass props down manually. Implementing a dark mode with Context for GatsbyJS requires a couple of additional steps (described in this post on the GatsbyJS blog).

The crucial elements are:

  1. Create a context component with an object for storing data that will be accessible across the app. The theme is created in this component along with a function for changing themes and a way to store the selected theme in local storage. The class is called ThemeProvider and wraps it’s children with ThemeContext.Provider.
  2. Make sure Gatsby can invoke the Context API by wrapping the root element in the ThemeProvider component in gatsby-browser.js.
  3. Make the Context theme is accessible to the Layout component by editing layout.js to wrap the render contents in ThemeContext.Consumer. This will allow the website to render with the correct style.
  4. In a separate file, create the component which will toggle the theme. The component must be wrapped in a ThemeContext.Consumer - this lets you subscribe to context changes within a component. This component should be imported somewhere sensible so the toggle is accessible to the user (I’ve imported it into my Layout component so it is always in the header bar). As a side note, I decided to use Material-UI for my toggle component, I’ll hopefully get round to writing a blog post about design systems and Material-UI at some point.
  5. Create styles. There are a couple of ways to do this - using CSS classes and storing distinct light and dark modes in a CSS file or using the Context object to store individual values (e.g. background-color, color, etc) which can then be set in the layout elements style attribute.

And hey guess what? This didn’t actually build!

So Gatsby failed to build after all that. I came across this post by Kim Hart that gave me something that worked. It involved using the use-dark-mode library and associated gatsby-plugin-use-dark-mode GatsbyJS plugin. It meant converting all my class components to functional components. But I’m glad I did that anyway, gives me access to hooks and they’re great! Something that was missing from Kim Hart’s blog post - the useDarkMode hook should be used with every component to query the darkMode.value state and allow different classNames to be set. For example, here is the component I use for the post cards on my homepage:

import React from 'react';
import {Link} from 'gatsby';
import useDarkMode from 'use-dark-mode';

const PostCard = (props) => {
  const {title, date, excerpt, link} = props;
  let initDarkMode = false;
  if (typeof window !== `undefined`) {
    initDarkMode = JSON.parse(localStorage.getItem('darkMode'))
  }
  const darkMode = useDarkMode(initDarkMode);

  return (
    <div className={darkMode.value ? 'postCard-dark' : 'postCard'}>
      <div className='link'>
        <Link to={link}>
          <h3>{title}</h3>
        </Link>
      </div>
      <h4>{date}</h4>
      <div>{excerpt}</div>
    </div>
  );
};

export default PostCard;

The className of the postCard div is being set dynamically depending on the dark mode state. The darkMode.value state is the same throughout the app and the dark mode state is still set in my theme toggling component, just updated to use the use-dark-mode method (details in Kim Hart’s blog post and use-dark-mode GitHub page).

You might have also noticed this little snippet in the above code:

let initDarkMode = false;
  if (typeof window !== `undefined`) {
    initDarkMode = JSON.parse(localStorage.getItem('darkMode'))
  }
  const darkMode = useDarkMode(initDarkMode);

Checking if the window is undefined before trying to access the local storage is crucial for Gatsby’s Server Side Rendering. It will not build without this check.

© 2021, Katie Baker