loading...
Cover image for Preferred Color Scheme in React

Preferred Color Scheme in React

httpjunkie profile image Eric Bishard ・4 min read

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;
  }
}

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";

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";

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 };

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.

Discussion

pic
Editor guide
Collapse
nilportugues profile image
Nil Portugués Calderó

Interesting! Thanks for sharing.

Collapse
austincondiff profile image
Austin Condiff

Can we trigger a re-render when the user changes their theme so you don't have to refresh for the new theme to take effect?

Collapse
httpjunkie profile image
Eric Bishard Author

When you change the preferred theme on your device, this is not something that the localStorage can be aware of without running the Media Query. For this reason, we simply check the theme preference whenever we load the app or refresh it. Just thinking about it logically right now, I think this is all we can do.

I have some ideas though... on how to watch and check for it more often, but it was beyond the scope of this small article. If I do write about that I will update this article with a link or solution I have come up with. Also any ideas are welcome.