DEV Community

Donte Ladatto
Donte Ladatto

Posted on

The Dark Side of useContext

      I don't know about you, but I'm a sucker for dark mode. So much so that the first thing I do whenever I open up a new app is look for a way to turn the lights out. In addition to being more aesthetically pleasing (everything on the page really pops against a dark backdrop), dark mode also makes navigation easier, with all the text and buttons shining forth like stars in the night sky. Aaand it's easier on the eyes if you yourself are in a literal dark place. With so many pros and absolutely zero cons, working this particular feature into your React application should be a no-brainer! Let's review a couple of (sort of) different ways to bring your app into the dark ages before moving on to what I consider the optimal solution.

useState: True or False

      In React, adding a dark mode toggle is as simple as creating a state variable that will be used to control the CSS styling being applied to the page. If you aren't familiar with the useState hook for React, check out the docs here. In this first method we will be using a Boolean value as a state variable:

// ./src/components/App

import React, { useState } from 'react'
import ChildComponent from './ChildComponent'

const App = () => {
  // Create state variable for dark mode with a default value
  // of false
  const [darkMode, setDarkMode] = useState(false)

  // Write callback function for the toggle button;
  // can also be written inline as 
  // () => setDarkMode(darkMode => !darkMode)
  function changeMode() {
    setDarkMode(darkMode => !darkMode)
  }

  return (
  // Use ternaries to dynamically update the div className
  // and button text
    <div className={darkMode ? "App Dark" : "App"}>
      <header>
        <h2>My App</h2>
        <button onClick={changeMode}>
          {darkMode ? "Light Mode" : "Dark Mode"}
        </button>
      </header>
  // Pass down state variable through props for children to use
      <ChildComponent darkMode={darkMode} />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

      This method is best for when you only want to use a single button or checkbox to switch between two modes. If you wanted to give the user more than two themes to choose from, however, a Boolean value simply won't cut it.

useState with a String Variable
// ./src/components/App

import React, { useState } from 'react'
import ChildComponent from './ChildComponent'

const App = () => {
  // Create state with a default value of "App",
  // the default option of your dropdown below
  const [theme, setTheme] = useState("App")

  // Write callback function for dropdown to update state
  function handleThemeChange(e) {
    setTheme(e.target.value)
  }
  return (
    <div className={theme}>
      <header>
        <h2>My App</h2>
        <select name="theme" onChange={handleThemeChange}>
  // Option values equal CSS classes (.App.Dark, etc)
          <option value="App">Select Theme</option>
          <option value="App Dark">Dark Mode</option>
          <option value="App Blue">Ocean</option>
          <option value="App Purple">Galaxy</option>
        </select>
      </header>
  // Pass down state value through props
      <ChildComponent theme={theme} />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

      Setting your state variable as a string will work whether you have two modes or twenty, giving you the flexibility to add more themes later on if you so choose.

      These methods work just fine if your styling is simple enough to be handled at the App level alone, or even in an extra child component or two. What if you're working with a more extensive component hierarchy, with many elements that require different CSS styling? Sure, you could pass down your state variable through generations of components using props, but that can become tedious and messy. Let's get acquainted with another React hook!

Add a Dark Mode to Your App with the useContext Hook

      The use of context in React is best reserved for situations where many components need access to the same data. Data like, I don't know, a state variable that controls what the entire app looks like? We'll start by creating a new JS file for the context we want to create, and then moving our state variable into it:

// ./src/context/theme

import React, { useState } from "react";
// Create a new Context object with... React.createContext()
const ThemeContext = React.createContext();

// Create your Provider component, and set its value to an
// object containing everything to be passed down to consumers
function ThemeProvider({ children }) {
// The state you created before now lives here
  const [theme, setTheme] = useState("App");

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}> 
    {children}
    </ThemeContext.Provider>
)}

export { ThemeContext, ThemeProvider }
Enter fullscreen mode Exit fullscreen mode

Then we'll want to wrap all components that need to use our new context with our provider. In this case we'll be going straight to our index.js file to wrap the App component with 'ThemeProvider':

// ./src/index

import React from "react";
import ReactDOM from "react-dom";
import App from "./components/App";
import "./index.css";
import { ThemeProvider } from './context/theme'

ReactDOM.render(
<ThemeProvider>
<App />
</ThemeProvider>,
document.getElementById("root"));
Enter fullscreen mode Exit fullscreen mode

Finally, each component that will be utilizing our theme context will have to import two new things: useContext from React, and ThemeContext. You then deconstruct the values you want to take from ThemeContext with the useContext hook and can use them freely:

// ./src/components/App

import React, { useContext } from "react";
import ChildComponent from "./ChildComponent";
import { ThemeContext } from "../context/theme";

function App() {
// Pass your new Context object (ThemeContext) as an
// argument to useContext, which returns the current value
// of the Context object, and destructure the data you need
  const { theme, setTheme } = useContext(ThemeContext)

  function handleThemeChange(e) {
    setTheme(e.target.value)
  }
  return (
    <div className={theme}>
      <header>
        <h2>My App</h2>
        <select name="theme" onChange={handleThemeChange}>
          <option value="App">Select Theme</option>
          <option value="App.Dark">Dark Mode</option>
          <option value="App.Blue">Ocean</option>
          <option value="App.Purple">Galaxy</option>
        </select>
      </header>
  // No passing props here! If ChildComponent needs to know
  // about theme, it can talk to our new friend Context
      <ChildComponent />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Gone are the days of passing down props, sometimes through components that don't even use them, to experience the glorious brilliance of dark mode (or any other user-selected themes) throughout your applications. Context grants us the ability to pump in our state and/or setter function wherever is necessary, giving us dark mode with cleaner code. Welcome to the dark ages... in a good way!

Top comments (0)