Preferred Color Scheme in React

Eric Bishard - Sep 29 '19 - - Dev Community

I have a demo that, on the fly, is able to swap the users theme from light to dark. It also remembers the persons last selected choice by saving that selection of 'dark' or 'light' to localStorage and checking there first when setting the theme on the next user visit.

This is great, but users of iOS, Windows 10, Android and such have an option to set their preferred theme. Here is how I can do that in my Windows 10 machine.

How to Read This Value in CSS (media queries)

I was reading this post here on the Dev Community about "Two Media Queries You Should Care About" and it talks about how to use the prefers-color-scheme media query:

@media (prefers-color-scheme: dark) {
  body {
    background: #111;
    color: #eee;
  }
}
Enter fullscreen mode Exit fullscreen mode

And this got me thinking. I already have the work done in my application to swap themes on the fly by user input (a switch) or by their last preferred selection that I have stored in localStorage.

But before I look for their preferred theme in localStorage or in conjunction with that, I should be querying about the preferred color scheme of their device. Since I only know how to do this in CSS, shown above, I don't want to write something super hacky in JS. I found this article: How to detect a user’s preferred color scheme in JavaScript which gives me a great idea of how to do this with React Hooks.

How to Read This Value in JS with a React Hook

In the above article, a section called "Reactive JS Approach" gave me an even better idea of using JavaScript's watchMedia() method. This is great, but I already use a React Hook in my project that wraps the watchMedia() method and exposes it as a React Hook.

This library is called: react-media-hook and can be used like so:

import { useMediaPredicate } from "react-media-hook";
let breakpoint = useMediaPredicate("(min-width: 600px)") ? "medium" : "small";
Enter fullscreen mode Exit fullscreen mode

In fact, that is exactly how I am using it for watching my 'small' vs 'medium' breakpoint. But instead of passing in a min-width query, I can pass in a prefers-color-scheme query instead.

const preferredTheme = useMediaPredicate("(prefers-color-scheme: dark)") ? "dark" : "light";
Enter fullscreen mode Exit fullscreen mode

This gives me an easy way to know if they prefer 'dark' vs 'light' on their device. I can keep this value as a string or boolean value and now I can easily determine this in my app with just a few lines of JS.

Below is an initial first stab at setting my theme. I use React ContextAPI and Hooks to set this value globally:

import React, { useState, useEffect, createContext } from 'react';
import { useMediaPredicate } from "react-media-hook";

const AppContext = createContext();

const AppProvider = props => {
  const preferredTheme = useMediaPredicate('(prefers-color-scheme: dark)') ? 'dark' : 'light'
  const [appData, setApp] = useState({

    navOpen: false,
    toggleSidenav: value => setApp(data => (
      { ...data, navOpen: value }
    )),

    themeMode: localStorage.getItem(''kr-todo-theme') || preferredTheme,
    changeTheme: mode => setApp(data => (
      {...data, themeMode: mode }
    )),

  });

  useEffect(() => {
    localStorage.setItem(''kr-todo-theme', appData.themeMode)
    }, [appData.themeMode]
  );

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

export { AppContext, AppProvider };
Enter fullscreen mode Exit fullscreen mode

To show this working, I can simulate the user hitting my app for the first time by removing their setting in localStorage. This will force my code to check the preferred theme, and base it's initial theme setting to 'dark' if they prefer that, otherwise 'light'.

Also, it will remember my last saved theme preference. So I like the idea of using the prefers-color-scheme as an indication as to what I should use so long as there are not user settings telling me they prefer otherwise.

I hope you enjoyed this article and if you would like to see the full demo application working with Kendo UI Sass theme Builder and KendoReact Components, you can get that here: GitHub.com/httpJunkie/kr-todo-hooks. It's the same application I used as a demo at ReactLiveNL in Amsterdam.

I also have an exhaustive article on how to work with React Hooks. I go over State and Effects, Context, Reducers, Custom Hooks and Managing Control State of Components.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .