How to Add Dark Mode to React with Context and Hooks
Tags: react, javascript, webdev
Published: 2020-03-02 10:17:31

More and more, we are seeing the dark mode feature in the apps that we are using every day. From mobile to web apps, the dark mode has become necessary for companies that want to take care of their user's eyes. Indeed, having a bright screen at night is really painful for our eyes. By turning (automatically) the dark mode helps reduce this pain and keep our users engage with our apps all night long (or not).

In this post, we are going to see how we can easily implement a dark mode feature in a ReactJS app. In order to do so, we'll leverage some React features like context, function components, and hooks.

Too busy to read the whole post? Have a look at the  [CodeSandbox demo](https://codesandbox.io/s/alterclass-darkmode-s9uru) to see this feature in action along with the source code.

 [![promo.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1582983872755/ux-qJiB3L.png)
](http://alterclass.io/) 

# What Will You Learn?

At this end of this post, you will be able to:

- Combine React `Context` and the `useReducer` hook to share a global state throughout the app.
- Use the `ThemeProvider` from the `styled-components` library to provide a theme to all React components within our app.
- Build a dark mode feature into your React app in an easy and non-intrusive way.

# What Will You Build?

In order to add the dark mode feature into our app, we will build the following features:

- A `Switch` component to be able to enable or disable the dark mode.
- A dark and light theme for our styled-components to consume.
- A global `Context` and `reducer` to manage the application state.

# Theme Definition

The first thing that we need for our dark mode feature is to define the light and dark themes of our app. In other words, we need to define the colors (text, background, ...) for each theme. 

Thanks to the `styled-components` library we are going to use, we can easily define our themes in a distinct file as JSON objects and provide it to the `ThemeProvider` later.

Below is the definition of the light and dark themes for our app:

```js
const black = "#363537";
const lightGrey = "#E2E2E2";
const white = "#FAFAFA";

export const light = {
  text: black,
  background: lightGrey
};

export const dark = {
  text: white,
  background: black
};
```

As you can notice, this is a really simplistic theme definition. It's up to you to define more theme parameters to style the app according to your visual identity.

Now that we have both our dark and light themes, we can focus on how we’re going to provide them to our app.

# Theme Provider

By leveraging the React Context API, the `styled-components` provides us a `ThemeProvider` wrapper component. Thanks to this component, we can add full theming support to our app. It provides a theme to all React components underneath itself. 

Let's add this wrapper component at the top of our React components' tree:

```js
import React from "react";
import { ThemeProvider } from "styled-components";

export default function App() {
  return (
    <ThemeProvider theme={...}>
      ...
    </ThemeProvider>
  );
};
```

You may have noticed that the `ThemeProvider` component accepts a theme property. This is an object representing the theme we want to use throughout our app. It will be either the light or dark theme depending on the application state. For now, let's leave it as is as we still need to implement the logic for handling the global app state.

But before implementing this logic, we can add global styles to our app.

# Global Styles

Once again, we are going to use the `styled-components` library to do so. Indeed, it has a helper function named `createGlobalStyle` that generates a styled React component that handles global styles.

```js
import React from "react";
import { ThemeProvider, createGlobalStyle } from "styled-components";

export const GlobalStyles = createGlobalStyle`...`;
```

By placing it at the top of our React tree, the styles will be injected into our app when rendered. In addition to that, we'll place it underneath our `ThemeProvider` wrapper. Hence, we will be able to apply specific theme styles to it. Let's see how to do it.

```js
export const GlobalStyles = createGlobalStyle`
  body, #root {
    background: ${({ theme }) => theme.background};
    color: ${({ theme }) => theme.text};
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    font-family: BlinkMacSystemFont, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  }
`;

export default function App() {
  return (
    <ThemeProvider theme={...}>
      <>
        <GlobalStyles />
        ...
      </>
    </ThemeProvider>
  );
};
```

As you can see, the global text and background color are provided by the loaded theme of our app.

It's now time to see how to implement the global state.

 [![promo.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1582983872755/ux-qJiB3L.png)
](http://alterclass.io/) 

# Global State

In order to share a global state that will be consumed by our components down the React tree, we will use the `useReducer` hook and the React `Context` API. 

As stated by the ReactJS documentation, `Context` is the perfect fit to share the application state of our app between components.

> Context provides a way to pass data through the component tree without having to pass props down manually at every level.

And the `useReducer` hook is a great choice to handle our application state that will hold the current theme (light or dark) to use throughout our app. 

This hook accepts a `reducer` and returns the current state paired with a `dispatch` method. The reducer is a function of type ```(state, action) => newState``` that manage our state. It is responsible to update the state depending on the type of action that has been triggered. In our example, we will define only one type of action called `TOGGLE_DARK_MODE` that will enable or disable the dark mode.

Let's create this reducer function in a separate file, `reducer.js`:

```js
const reducer = (state = {}, action) => {
  switch (action.type) {
    case "TOGGLE_DARK_MODE":
      return {
        isDark: !state.isDark
      };
    default:
      return state;
  }
};

export default reducer;
```

As you may have noticed, our state is holding a single boolean variable `isDark`. If the `TOGGLE_DARK_MODE` action is triggered, the reducer updates the `isDark` state variable by toggling is value. 

Now that we have our `reducer` implemented we can create our `useReducer` state and initialize it. By default, we will disable the dark mode.

```js
import React, { useReducer } from "react";
import reducer from "./reducer";

export default function App() {
  const [state, dispatch] = useReducer(reducer, {
    isDark: false
  });

  ...
};
```

The only missing piece in our global state implementation is the Context. We'll also define it in a distinct file and export it, `context.js`:

```js
import React from "react";

export default React.createContext(null);
```

Let's now combine everything together into our app and use our global state to provide the current theme to the `ThemeProvider` component.

```js
import React, { useReducer } from "react";
import { ThemeProvider, createGlobalStyle } from "styled-components";
import { light, dark } from "./themes";
import Context from "./context";
import reducer from "./reducer";

...

export default function App() {
  const [state, dispatch] = useReducer(reducer, {
    isDark: false
  });

  return (
    <Context.Provider value={{ state, dispatch }}>
      <ThemeProvider theme={state.isDark ? dark : light}>
        <>
          <GlobalStyles />
          ...
        </>
      </ThemeProvider>
    </Context.Provider>
  );
};
```

As you can see the `Context` is providing, through its `Provider`, the current application state and the dispatch method that will be used by other components to trigger the `TOGGLE_DARK_MODE` action.

# The Switch Component

Well done 👏👏 on completing all the steps so far. We are almost done. We’ve implemented all the logic and components needed for enabling the dark mode feature. Now it’s time to trigger it in our app.

To do so, we'll build a `Switch` component to allow users to enable/disable dark mode. Here's the component itself:

```js
import React from "react";
import Context from "./context";
import styled from "styled-components";

const Container = styled.label`
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
  margin-right: 15px;
`;

const Slider = styled.span`
  position: absolute;
  top: 0;
  display: block;
  cursor: pointer;
  width: 100%;
  height: 100%;
  background-color: #ccc;
  border-radius: 34px;
  -webkit-transition: 0.4s;
  transition: 0.4s;

  &::before {
    position: absolute;
    content: "";
    height: 26px;
    width: 26px;
    margin: 4px;
    background-color: white;
    border-radius: 50%;
    -webkit-transition: 0.4s;
    transition: 0.4s;
  }
`;

const Input = styled.input`
  opacity: 0;
  width: 0;
  height: 0;
  margin: 0;

  &:checked + ${Slider} {
    background-color: #2196f3;
  }

  &:checked + ${Slider}::before {
    -webkit-transform: translateX(26px);
    -ms-transform: translateX(26px);
    transform: translateX(26px);
  }

  &:focus + ${Slider} {
    box-shadow: 0 0 1px #2196f3;
  }
`;

const Switch = () => {
const { dispatch } = useContext(Context);

  const handleOnClick = () => {
    // Dispatch action
    dispatch({ type: "TOGGLE_DARK_MODE" });
  };

  return (
    <Container>
      <Input type="checkbox" onClick={handleOnClick} />
      <Slider />
    </Container>
  );
};

export default Switch;
```

Inside the `Switch` component, we are using the `dispatch` method from the `Context` to toggle the dark mode theme.

Finally, let's add it to the app.

```js
export default function App() {

  ...

  return (
    <Context.Provider value={{ state, dispatch }}>
      <ThemeProvider theme={state.isDark ? dark : light}>
        <>
          <GlobalStyles />
          <Switch />
        </>
      </ThemeProvider>
    </Context.Provider>
  );
};
```

# Conclusion

Dark mode has been a highly requested feature, and we successfully added support for it in our React application by using some of the latest React features. I hope this post will help you add dark mode capability to your app and save the eyes of your users.

 [![promo.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1582983872755/ux-qJiB3L.png)
](http://alterclass.io/)