Navigate back to the homepage
BLOG
Try NowLogin
Back

Using React Context API with Gatsby

Muhammad Muhsin
July 7th, 2020 · 3 min read

You often feel the unsettling flash of a bright phone screen while relaxing in a dimly lit room. This is alleviated by introducing a “dark mode” which switches background and foreground colors to reduce eye strain. I decided to add this to my boutique web agency Laccadive IO’s new Gatsby-based site.

One of the few types of alternative theme that adds real value to users is a low light intensity “night mode” theme. Not only is it easier on the eyes when reading in the dark, but it also reduces the likelihood of migraine and the irritation of other light sensitivity disorders. As a migraine sufferer, I’m interested!

Heydon Pickering

In considering the different ways to implement this a natural fit become apparent: React’s new Context API. Having worked with Context API before, this seemed like a particularly well suited use for this API. However, I soon realized I would need to do a little set-up work to get this up and running.

After a brief search, I came across just what I was looking for, the Gatsby Browser APIs. Specifically, the wrapRootElement API was a perfect fit for this use case. This API allows you to wrap your root element with a wrapping component, e.g. a Provider from Redux or… a ThemeProvider from React Context. Using this, I managed to achieve dark mode for my use case.

Let’s do a deep dive into how this feature was actually implemented step by step using React Context, Gatsby, and a Theme Provider to implement a dark mode UI!

Creating the Context File in a New Gatsby Project

First of all, you have to initialize a Gatsby project and start it in develop mode.

  1. gatsby new gatsby-dark-mode
  2. cd gatsby-dark-mode
  3. npm start

Then, create a context folder within src and the ThemeContext.js file within it.

1import React from "react"
2
3const defaultState = {
4 dark: false,
5 toggleDark: () => {},
6}
7
8const ThemeContext = React.createContext(defaultState)
9
10// Getting dark mode information from OS!
11// You need macOS Mojave + Safari Technology Preview Release 68 to test this currently.
12const supportsDarkMode = () =>
13 window.matchMedia("(prefers-color-scheme: dark)").matches === true
14
15class ThemeProvider extends React.Component {
16 state = {
17 dark: false,
18 }
19
20 toggleDark = () => {
21 let dark = !this.state.dark
22 localStorage.setItem("dark", JSON.stringify(dark))
23 this.setState({ dark })
24 }
25
26 componentDidMount() {
27 // Getting dark mode value from localStorage!
28 const lsDark = JSON.parse(localStorage.getItem("dark"))
29 if (lsDark) {
30 this.setState({ dark: lsDark })
31 } else if (supportsDarkMode()) {
32 this.setState({ dark: true })
33 }
34 }
35
36 render() {
37 const { children } = this.props
38 const { dark } = this.state
39 return (
40 <ThemeContext.Provider
41 value={{
42 dark,
43 toggleDark: this.toggleDark,
44 }}
45 >
46 {children}
47 </ThemeContext.Provider>
48 )
49 }
50}
51
52export default ThemeContext
53
54export { ThemeProvider }

React.createContext is a new function in React 16.3 and allows you to create a Context object. It accepts a default state, the value which will be used when a component does not have a matching Provider above it in the tree. The function returns an object with Provider and Consumer properties which we will be using later.

Create the ThemeProvider component which wraps its children with ThemeContext.Provider. This component is exported as a named export from the file.

The toggleDark function gets the current state.dark value and switches the value to the opposite. It then stores the new value in localStorage before setting it back to state using the setState function, so that it persists over page refreshes. The dark value from state and the toggleDark function are passed to the Provider.

Modifying the Gatsby Browser File

Next, write the following code within the gatsby-browser.js file, which is in the root folder in a Gatsby project:

1import React from "react"
2
3import { ThemeProvider } from "./src/context/ThemeContext"
4
5// highlight-start
6export const wrapRootElement = ({ element }) => (
7 <ThemeProvider>{element}</ThemeProvider>
8)
9// highlight-end

The ThemeProvider component exported from the ThemeContext.js file wraps the root element and is exported as wrapRootElement. This API is then invoked appropriately by the Gatsby API runner.

Editing the Layout File

The default layout.js uses a <staticQuery> and renderProp to render the layout, which is wrapped by a Fragment <>. Modify it to look like this:

1import * as React from 'react'
2import PropTypes from 'prop-types'
3import { StaticQuery, graphql } from 'gatsby'
4
5import ThemeContext from '../context/ThemeContext'
6import Header from './header'
7import './layout.css'
8
9const Layout = ({ children }) => (
10 <StaticQuery
11 query={graphql`
12 query SiteTitleQuery {
13 site {
14 siteMetadata {
15 title
16 }
17 }
18 }
19 `}
20 render={data => (
21 {/* highlight-start */}
22 <ThemeContext.Consumer>
23 {theme => (
24 <div className={theme.dark ? 'dark' : 'light'}>
25 {/* highlight-end */}
26 <Header siteTitle={data.site.siteMetadata.title} />
27 <div
28 style={{
29 margin: `0 auto`,
30 maxWidth: 960,
31 padding: `0px 1.0875rem 1.45rem`,
32 paddingTop: 0,
33 }}
34 >
35 {children}
36 <footer>
37 © {new Date().getFullYear()}, Built with
38 {` `}
39 <a href="https://www.gatsbyjs.org">Gatsby</a>
40 </footer>
41 </div>
42 </div>
43 )}
44 </ThemeContext.Consumer>
45 )}
46 />
47)
48
49Layout.propTypes = {
50 children: PropTypes.node.isRequired,
51}
52
53export default Layout

The class of the wrapper div will change based on the context value of the dark variable, which we set as state in the ThemeContext.js file.

Adding the Switch in the Header

With this configuration completed, we can now add the actual switch to toggle dark mode. Modify the header.js component, like so:

1import { Link } from "gatsby"
2import PropTypes from "prop-types"
3import React from "react"
4
5import ThemeContext from "../context/ThemeContext"
6
7const Header = ({ siteTitle }) => (
8 <ThemeContext.Consumer>
9 {theme => (
10 <div
11 style={{
12 background: `rebeccapurple`,
13 marginBottom: `1.45rem`,
14 }}
15 >
16 <div
17 style={{
18 margin: `0 auto`,
19 maxWidth: 960,
20 padding: `1.45rem 1.0875rem`,
21 }}
22 >
23 <h1 style={{ margin: 0 }}>
24 <Link
25 to="/"
26 style={{
27 color: `white`,
28 textDecoration: `none`,
29 }}
30 >
31 {siteTitle}
32 </Link>
33 </h1>
34 <button className="dark-switcher" onClick={theme.toggleDark}>
35 {theme.dark ? <span>Light mode ☀</span> : <span>Dark mode ☾</span>}
36 </button>
37 </div>
38 </div>
39 )}
40 </ThemeContext.Consumer>
41)
42
43Header.propTypes = {
44 siteTitle: PropTypes.string,
45}
46
47Header.defaultProps = {
48 siteTitle: ``,
49}
50
51export default Header

Adding Styles

At this point, we’ve set up a dark mode toggle and conditionally render a className if dark mode is enabled. However, we still need to actually style based upon this conditional className. As such, we need to add the following styles in the layout.css file:

1/* Dark mode styles */
2.dark-switcher {
3 color: #fff;
4 margin-top: 5px;
5 background: transparent;
6 border: none;
7 cursor: pointer;
8}
9
10@media (min-width: 992px) {
11 .dark-switcher {
12 position: absolute;
13 right: 15px;
14 top: 10px;
15 }
16}
17
18.dark {
19 background-color: #2a2b2d;
20 color: #fff;
21 transition: all 0.6s ease;
22}
23
24.light {
25 transition: all 0.6s ease;
26 background-color: #fefefe;
27}
28
29.dark a,
30.dark a:visited {
31 color: #fff;
32}

Conclusion

In just a few, simple steps we’ve enabled a conditional dark mode that our users will certainly appreciate. We’ve leveraged APIs like Context in React, as well as internal Gatsby APIs to wrap our code with a provider. Now if you visit http://localhost:8000/ you can see all of our work pay off!

We covered the following in today’s article:

  • Introduction to dark mode in web development
  • Initializing a Gatsby project
  • Initializing the context object with createContext
  • Using the Gatsby Browser API and returning wrapRootElement from gatsby-browser.js
  • Wrapping the JSX within layout.js with a Context Consumer and a div with class referring to the dark mode state
  • Adding the switch inside the header
  • Adding the styles relevant to the Dark mode

Interested in seeing this in action? Head over to https://github.com/m-muhsin/gatsby-dark-mode and clone or fork my project.

About the Author

Author_Muhammad-Muhsin

Muhammad Muhsin is a full-stack engineer from beautiful Sri Lanka who fell in love with React and WordPress during his college days. He writes about these topics on publications like Smashing Magazine, the GatsbyJS blog and speaks about them in conferences like WordCamp US/Asia, WordSesh, WooSesh and JS for WP Conf. His go-to stack for most projects is Gatsby + WordPress and uses other technologies where suitable.

Read the original article or more interesting posts on Muhammad Muhsin’s blog.

Frontend Monitoring

Asayer is a frontend monitoring tool that replays everything your users do and shows how your web app behaves for every issue. It’s like having your browser’s inspector open while looking over your user’s shoulder.

text

Asayer lets you reproduce issues, aggregate JS errors and monitor your web app’s performance. Happy debugging, for modern frontend teams - Start monitoring your web app for free.

More articles from Asayer Blog

How to Debug Angular Apps with Chrome DevTools

The tools and tactics Angular developers use for debugging their applications in production, with a focus on Chrome DevTools.

April 18th, 2020 · 8 min read

Rethinking Frontend Monitoring

Frontend monitoring and the increase in complexity due to modern application stack and the blind spots in frontend monitoring.

April 10th, 2020 · 4 min read
© 2020 Asayer Blog
Link to $https://twitter.com/asayerioLink to $https://github.com/asayerioLink to $https://www.linkedin.com/company/18257552