Creating a dark mode for your React app with useContext()

Mark Sauer-Utley
Jun 14, 2019 · 4 min read
Image for post
Image for post
my portfolio site in light and dark mode

I’m a huge fan of dark mode. I have dark themes for everything on my mac: VSCode, Chrome, Alfred, all of it. My background on my desktop is just the color black. So it seemed natural that I should add a dark mode to my portfolio page. I had a bit of trouble finding a good guide on how to go about updating the context of my app, since useContext()provides a read-only value. But after a few minutes of hacking and digging around stack overflow, I got it working and thought I would share here.

What is Context?

How will we do this?

import styled from 'styled-components';const Layout = styled.div`
background-color: ${props => (
props.darkMode ? "black" : "white"
)};
color: ${props => props.darkMode ? "white" : "black"};
`

and be implemented like this:

export default props => {
... some code to get the darkMode value
return (
<Layout darkMode={darkMode}>
{props.children}
</Layout>
)}

Creating your context

import { createContext } from "react";export const DarkModeContext = createContext({
darkMode: true,
toggleDarkMode: () => {},
});

The Context object returned from createContext will provide us with a Provider and a Consumer.

The Provider is going to be the thing that holds the context for the Consumers. The Consumers subscribe to the context and receive it from the nearest Provider in the component tree. So when the Provider updates the context, any Consumer will be notified of the change and re-render with the new Context values.

The useContext() hook will take the DarkModeContext as an argument and return the values held in that context by the nearest Provider. It essentially creates a Consumer in the component in which it is called.

Creating a Provider

** /components/ContextWrapper.js **import { useState } from "react";
import { DarkModeContext } from "../hooks/DarkModeContext";
export default props => {
const [darkMode, setDarkMode] = useState(false);

function toggleDarkMode() {
setDarkMode(darkMode => !darkMode);
}
return (
<DarkModeContext.Provider value={{ darkMode, toggleDarkMode }}> . {props.children}
</DarkModeContext.Provider>
);
};

So here, we are creating a component whose state is going to hold the value of the darkMode context. The component itself has a toggle function that allows us to update the darkMode value in that component’s state. It will then return the DarkModeContext.Provider, passing it the values of darkMode and toggleDarkMode from it.

So when this wrapper component renders its children, the children will be able to use the useContext() hook to become Consumers and subscribe to this Provider, thus gaining access to both darkMode and toggleDarkMode().

Subscribing to the Provider with useContext()

** App.js **import ContextWrapper from "../components/ContextWrapper";
import Layout from "../components/Layout";
export default () => (
<ContextWrapper>
<Layout>
... your app
</Layout>
</ContextWrapper>
);

Now let’s add the useContext() hook to that Layout component, allowing it to become a Consumer of the DarkModeContext.

** /components/Layout.js **import { useContext } from 'react';
import styled from 'styled-components';
import { DarkModeContext } from '../hooks/DarkModeContext';
const Layout = styled.div`
background-color: ${props => (
props.darkMode ? "black" : "white"
)};
color: ${props => props.darkMode ? "white" : "black"};
`
export default props => {
const { darkMode } = useContext(DarkModeContext);
return (
<Layout darkMode={darkMode}>
{props.children}
</Layout>
)}

Now, the Layout is consuming the context from the Provider we made in our ContextWrapper component. Not only the Layout, but any component that is a child (or grandchild or great-grandchild, etc.) of the ContextWrapper component will be able to subscribe directly to the Provider, with no need to pass the data through each parent component. Sweet!

Updating the Context

** /components/DarkModeSwitch.js **import { useContext } from "react";
import styled from "styled-components";
import { DarkModeContext } from "../hooks/DarkModeContext";
export default () => {
const { darkMode, toggleDarkMode } = useContext(DarkModeContext);
return (
<button onClick={toggleDarkMode}>
click for {darkMode ? "light mode" : "dark mode"}
</button>
);
};

And render it anywhere in the component tree as long is it is a descendent of the ContextWrapper component (our Provider).

That’s all

You can also see the code for the whole project in this repo if you want to see it all put together.

The Startup

Medium's largest active publication, followed by +719K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store