DEV Community

Cover image for How to implement UI theme switching with Recoil?
Konstantin Tarkus
Konstantin Tarkus

Posted on • Edited on

How to implement UI theme switching with Recoil?

NOTE: This tutorial is relevant to developers using a CSS-in-JS styling approach, e.g. Emotion.js, Material UI, etc.

Step 1: Use Recoil atom for the currently selected theme's name

import { PaletteMode } from "@mui/material";
import { atom } from "recoil";

export const ThemeName = atom<PaletteMode>({
  key: "ThemeName",
  effects: [/* ... */]
});
Enter fullscreen mode Exit fullscreen mode

Step 2: Use Recoil selector for the currently selected theme object

import { createTheme } from "@mui/material";
import { selector } from "recoil";

export const Theme = selector({
  key: "Theme",
  dangerouslyAllowMutability: true,
  get(ctx) {
    const name = ctx.get(ThemeName);
    return createTheme(/* ... */);
  },
});
Enter fullscreen mode Exit fullscreen mode

Step 3: Add useTheme(), useToggleTheme() React hooks

import { useRecoilValue, useRecoilCallback } from "recoil";

export function useTheme() {
  return useRecoilValue(Theme);
}

export function useToggleTheme() {
  return useRecoilCallback(
    (ctx) => () => {
      ctx.set(ThemeName, (prev) => (prev === "dark" ? "light" : "dark"));
    },
    []
  );
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Save/restore the currently selected theme name to/from localStorage

export const ThemeName = atom<PaletteMode>({
  key: "ThemeName",
  effects: [
    (ctx) => {
      const storageKey = "theme";

      if (ctx.trigger === "get") {
        const name: PaletteMode =
          localStorage?.getItem(storageKey) === "dark"
            ? "dark"
            : localStorage?.getItem(storageKey) === "light"
            ? "light"
            : matchMedia?.("(prefers-color-scheme: dark)").matches
            ? "dark"
            : "light";
        ctx.setSelf(name);
      }

      ctx.onSet((value) => {
        localStorage?.setItem(storageKey, value);
      });
    },
  ],
});
Enter fullscreen mode Exit fullscreen mode

Step 5: Add ThemeProvider to the top-level React component

import { ThemeProvider } from "@mui/material";
import { useTheme } from "../theme/index.js";

function App(): JSX.Element {
  const theme = useTheme();

  return (
    <ThemeProvider theme={theme}>
      {/* ... */}
    </Theme>
  );
}
Enter fullscreen mode Exit fullscreen mode

See app/theme/index.ts in React Starter Kit

Resources

Top comments (0)