0

I am managing the theme state as an atom of recoil.

If the user changes the theme through the ui, the corresponding atom state will change.

I want to save the theme value changed here to localstorage. To do that, we need to know when that state changes.

Of course, you can add localstorage storage code before calling the corresponding atom state change, but this is too inefficient if atom is used in many places.

Should I use a selector at this point? However, since a theme is a single state, it is not intuitive to treat it as a derived state to extend the state change logic.

Any help would be appreciated if you know.

example code

import { StrictMode } from "react";
import ReactDOM from "react-dom";
import { RecoilRoot, atom, useRecoilState } from "recoil";

type ThemeItem = "light" | "dark";
// : I want to do some extra work when this state changes (ex: Save the changed state to localStorage)
const themeState = atom<ThemeItem>({
  key: "themeState",
  default: "light",
});

function App() {
  const [theme, setTheme] = useRecoilState(themeState);

  function handleTheme(theme: ThemeItem) {
    return () => setTheme(theme);
  }

  return (
    <>
      <h3>{theme}</h3>
      <button onClick={handleTheme("light")}>light</button>
      <button onClick={handleTheme("dark")}>dark</button>
    </>
  );
}

ReactDOM.render(
  <StrictMode>
    <RecoilRoot>
      <App />
    </RecoilRoot>
  </StrictMode>,
  document.getElementById("root")
);
Kyungeun Park
  • 113
  • 1
  • 10

2 Answers2

1

The way I've solved this before is by implementing a ThemeProvider (or whatever you want to call it), and then using useEffect both store and load values from localStorage:

import { StrictMode, useEffect } from "react";
import ReactDOM from "react-dom";
import { RecoilRoot, atom, useRecoilState } from "recoil";

type ThemeItem = "light" | "dark";
// : I want to do some extra work when this state changes (ex: Save the changed state to localStorage)
const themeState = atom<ThemeItem>({
  key: "themeState",
  default: "light",
});

function ThemeProvider() {
  const [theme, setTheme] = useRecoilState(themeState);

  useEffect(() => {
    // Save theme in localStorage on change
    localStorage.setItem('theme', theme);
  }, [theme]);

  useEffect(() => {
    // Load theme from localStorage on mount
    if (!localStorage.getItem('theme')) return;
    setTheme(localStorage.getItem('theme') as ThemeItem);
  }, []);
}

function App() {
  const [theme, setTheme] = useRecoilState(themeState);

  function handleTheme(theme: ThemeItem) {
    return () => setTheme(theme);
  }

  return (
    <>
      <h3>{theme}</h3>
      <button onClick={handleTheme("light")}>light</button>
      <button onClick={handleTheme("dark")}>dark</button>
    </>
  );
}

ReactDOM.render(
  <StrictMode>
    <RecoilRoot>
      <ThemeProvider />
      <App />
    </RecoilRoot>
  </StrictMode>,
  document.getElementById("root")
);
Olian04
  • 6,480
  • 2
  • 27
  • 54
  • Oh right, but in the end, it is controlled by the LifeCycle of react, is it impossible to solve it with the function of recoil? Of course, I know that recoil is a state management library for React, but it feels like react is interfering. Or am I thinking about something impossible? – Kyungeun Park Sep 11 '21 at 13:22
  • 2
    You could use "effects". They are experimental, so I wouldn't recommend it. https://recoiljs.org/docs/guides/atom-effects – Olian04 Sep 11 '21 at 13:31
0

In 2022, the canonical answer would be to use Recoil effects: https://recoiljs.org/docs/guides/atom-effects

This would allow you to do something such as the following:

const themeState = atom<ThemeItem>({
  key: "themeState",
  default: "light",
  effects: [
    ({ setSelf }) => {
      if (localStorage.getItem('theme')) {
        setSelf(localStorage.getItem('theme');
      }
    },
    ({ onSet }) => {
      onSet(newTheme => { localStorage.setItem('theme', newTheme });
    },
  ],
});

This is very similar to converting @Olian04's answer to use the built in Recoil effects rather than relying on Reacts, which I believe to be more idiomatic.