Light/Dark mode toggle with React using Context API and styled components
Tags: react, javascript
Published: 2020-05-11 15:15:14

Have you ever wonder how to build a dark/light theme with React? after checking a couple of solution I've decided to build a basic and simple theme switcher from scratch using Context API and styled-components. 

This is just one way of doing it from many ..many more 🤘💥. If you are curious about what we are gonna build here you can see the [live demo](https://theme-toggle.kishsorin.now.sh/) of the final version and full code from github [here](https://github.com/SorinC6/toggle-theme-example).


If you don't already have an ReactJS application you can easily create one with [create-react-app](https://create-react-app.dev/docs/getting-started/).
 
 Once you have your react app running you will have to install 3 packages:
  - [styled-components](https://styled-components.com/) are one of the new ways to use CSS in modern JavaScript. It is the meant to be a successor of CSS Modules, a way to write CSS that's scoped to a single component, and not leak to any other element in the page.
  - [react-switch](https://www.npmjs.com/package/react-switch) we will use this library for switch button
  - [react-icons](https://www.npmjs.com/package/react-icons) popular icons in your React projects easily, we will use it for bringing light and dark icons.

First we will create a component AppProvider.js in which we will define the initial state of our theme mode using useState and then create an application context with createContext function. 
>
Context provides a way to pass data through the component tree without having to pass props down manually at every level. The theme mode will be available through useContext hook.
>
   
Every Context object comes with a Provider React component that accepts a value prop. This value will be passed to down to all Provider descendants.

```javascript
import React, { createContext, useState } from "react";

export const AppContext = createContext();

const AppProvider = ({ children }) => {
  const [themeMode, setThemeMode] = useState("lightTheme");

  const value = { themeMode };

  return <AppContext.Provider value={value}>{children}</AppContext.Provider>;
};

export default AppProvider;
```

Once we have the AppProvider we can simply import it in index.js file and wrap all our application with it

```javascript
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import AppProvider from "./AppProvider";

ReactDOM.render(
  <AppProvider>
    <App />
  </AppProvider>,
  document.getElementById("root")
```
Now it will be a perfect time to check if our Provider it's doing its job by bringing the initial theme mode in one of the Provider descendants. Let's create a new component ThemeSwitcher.js and import it in App.js

```javascript
import React from "react";
import ThemeSwitcher from "./ThemeSwitcher";
function App() {
  return (
    <>
      <ThemeSwitcher />
    </>
  );
}

export default App;

```

Using useContext hook we we will gain access to our initial theme mode `lightTheme`. Now you can really see the power of context API - we can pass the state and manage it without importing any library 👏💪

```javascript
import React, { useContext } from "react";
import { AppContext } from "./AppProvider";

const ThemeSwitcher = () => {
  const { themeMode } = useContext(AppContext);
  console.log("THEME MODE: ", themeMode);
  return <div></div>;
};

export default ThemeSwitcher;
``` 

Once we check that everything it's working we will start creating the theme colors and global styles for our beautiful application. Let's add a folder called styles in our src folder and create a `theme.js` file.

`theme.js` - in which will keep our theme colors

```javascript
export default {
  lightTheme: {
    colors: {
      background: 'radial-gradient(lightGrey, black)',
      black: 'black',
      white: 'white',
      blue: '#a0e9fd',
      lightBlue: '#caf3fe',
      secondary: 'radial-gradient(green, yellow)',
    },
    transition: '0.3s',
  },
  darkTheme: {
    colors: {
      background: 'radial-gradient(black, lightGrey)',
      black: 'white',
      white: 'black',
      blue: '#a0e9fd',
      lightBlue: '#caf3fe',
      secondary: 'radial-gradient(yellow, green)',
    },
    transition: '0.3s',
  },
}
```

After creating the theme we will need to import this file in our provider and bring the `ThemeProvider` from [styled-components](https://styled-components.com/docs/advanced)

```javascript
import { ThemeProvider } from 'styled-components'
import { theme } from './styles'
```

Now we are ready to wrap our application with the `ThemeProvider`. Create a costumTheme  constant and with the help of our useState hook (which its keeping our theme mode - lightTheme that is hardcoded for now) we can get the specific colors from theme object

```javascript
const AppProvider = ({ children }) => {
  const [themeMode, setThemeMode] = useState("lightTheme");

  const value = { themeMode };
  const costumTheme = theme[themeMode];

  return (
    <AppContext.Provider value={value}>
      <ThemeProvider theme={costumTheme}>
        {children}
      </ThemeProvider>
    </AppContext.Provider>
  );
};
```

Perfect time to check if out theme provider it's working. For verify this really simply we can create a global file in styles folder and bring some of the theme colors here.

```javascript
import { createGlobalStyle, css } from 'styled-components'

export default createGlobalStyle`
${({ theme }) => css`
  html {
    height: 100%;

    body {
      display: flex;
      flex-direction: column;
      height: 100%;
      margin: 0;

      #root {
        background: ${theme.colors.background};
        color: ${theme.colors.black};
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        font-family: sans-serif;
        height: 100%;
        padding: 15px;
      }
    }
  }
`}
`
```

Notice now that our theme colors are available in the entire application. 
We can create an index.js file in the styles folder and export both global and theme files.

```javascript
export { default as GlobalStyles } from "./global";
export { default as theme } from "./theme";
```

Once we bring `GlobalStyles` in AppProvider component and add it bellow the `ThemeSwitcher` our application background will take the styles corresponding to `lightTheme`

- import Global styles in AppProvider
```javascript
import { GlobalStyles, theme } from "./styles";
```

- add global styles
```javascript
 <ThemeProvider theme={costumTheme}>
      <GlobalStyles />
      {children}
 </ThemeProvider>
```

Now let's create a function for toggle the theme mode. We will check the previous state and change it base on the current mode

```javascript
const toggleTheme = () => {
    setThemeMode(prevState => {
      if (prevState === 'lightTheme') {
        return 'darkTheme'
      } else {
        return 'lightTheme'
      }
    })
  }
```

Add this function in value object. After this the toggleTheme function will be available in the entire application

```javascript
  const value = { toggleTheme, themeMode }
```

Final step is to bring this function in `ThemeSwitcher` component and execute it. Using Context API bring `toggleTheme` from the context and Switch component from `react-switch`. 
Now all the magic its handle by Switch components. After reading the documentation we will know that it can receive props like as:
- checked - receive true or false, we will check if the theme its light or dark;
- height;
- width;
- checkedIcon - it can receive an icon, so we will import `IoMdSunny` for light icon from 'react-icons';
- uncheckedIcon - it can receive an icon, so we will import `IoMdMoon` for light icon from 'react-icons';
- onChange - invoked when the user clicks or drags the switch;

```javascript
import React, { useContext } from "react";
import styled from "styled-components";
import { AppContext } from "./AppProvider";
import Switch from "react-switch";

const ThemeSwitcher = () => {
  const { toggleTheme, themeMode } = useContext(AppContext);
  const handleThemeChange = (e) => {
    toggleTheme();
  };
  return (
    <Root>
      <h1>Toggle Theme</h1>
      <Switch
        checked={themeMode === "lightTheme" ? true : false}
        className="test"
        height={50}
        width={120}
        checkedIcon={
          <Sun
            style={{
              display: "flex",
              justifyContent: "center",
              alignItems: "center",
              height: "100%",
              fontSize: 35,
              paddingLeft: 10,
            }}
            color={themeMode === "lightTheme" ? "white" : "grey"}
            className="light"
          />
        }
        uncheckedIcon={
          <Moon
            style={{
              display: "flex",
              justifyContent: "center",
              alignItems: "center",
              height: "100%",
              fontSize: 35,
              paddingLeft: 24,
            }}
            color={themeMode === "darkTheme" ? "blue" : "blue"}
            className="dark"
          />
        }
        onChange={handleThemeChange}
      />
    </Root>
  );
};

export default ThemeSwitcher;

const Root = styled.div`
  display: flex;
  flex-direction: column;
  align-items: center;
  .custom-classname .react-toggle--checked .react-toggle-track {
    background-color: red;
    height: 200px;
    width: 200px;
    padding: 30px;
  }
`;

```
For future improvements we can take advantage of the local storage and persist light/dark mode. We will make small changes in AppProvider component. 

First we will need to check if there is a theme in localStorage or not, then set the initial themeMode with it, if not we will set it by default with `lightTheme`

```javascript
const [themeMode, setThemeMode] = useState(
    localStorage.getItem("theme") || "lightTheme"
  );
```

Next we will bring useEffect and save the `themeMode` in localStore every time we change it

```javascript
  useEffect(() => {
    localStorage.setItem("theme", themeMode);
  }, [themeMode]);
```

>
Add themeMode in the dependency array. The array is the second optional argument in the useEffect hook. As the name implies, it is an array of dependencies that, when changed from the previous render, will recall the effect function defined in the first argument
>

###Conclusion

As this it's just a basic example how you can achieve this functionality, on a bigger application the complexity will increase and probably needs more work into it.  
I would appreciate any feedback, good or bad, in order to improve the next articles.
Thanks for reading and happy coding! :)