javascriptreactjsnext.jsrecoiljs

Recoil: Triggering a function on atom change


I'm trying to store a user object inside an atom and have it be cached inside localStorage each time it changes because the app might crash and I do not want the user to have to sign in every time:

localStorage.setItem('user', JSON.stringify(user))

Previously when I used useContext this was achieved through a useEffect in the parent component (App.js) that monitored the user object for changes. It's no longer possible for me to do this after migrating from useContext to Recoil due to my NextJS project structure (RecoilRoot wraps around the children component, so the parent App.js itself does not have access to useRecoil methods). And so, I'm wondering if there's a way to implement this kind of listener or callback using a built-in Recoil method

Desired process:

  1. Declare the state: const [user, setUser] = useRecoilState(userAtom)
  2. Trigger a change setUser({...})
  3. Trigger a callback on state change localStorage.setItem('user', JSON.stringify(user))

Solution: There's a built-in effects hook in the atom() constructor

Docs: https://recoiljs.org/docs/guides/atom-effects/ (Refer to 'Logging Example')

export const userAtom = atom({
    key: 'user',
    default: null,
    effects: [
        ({onSet}) => {
            onSet(data => {
                localStorage.setItem('user', JSON.stringify(data))
                console.log("Updated User Data (state.tsx): ", data)
            })
        }
    ]
})


Solution

  • You can use the useEffect hook, which lets you do something extra when your atom state changes. You can get the current value of your atom state with the useRecoilValue hook, and then pass it as a dependency to useEffect. Inside useEffect, you can save the user object in localStorage so you don't lose it. For example:

    import { useRecoilValue } from "recoil";
    
    const userAtom = atom({
      key: "userAtom",
      default: null,
    });
    
    const userValue = useRecoilValue(userAtom);
    
    useEffect(() => {
      localStorage.setItem("user", JSON.stringify(userValue));
    }, [userValue]);