javascriptnode.jsreactjstypescriptrecoiljs

How to update atoms (state) in Recoil.js outside components ? (React)


I'm new to Recoil.js, I have the following atom and selector for the signed-in user in the app:

const signedInUserAtom = atom<SignedInUser | null>({
    key: 'signedInUserAtom',
    default: null
})

export const signedInUserSelector = selector<SignedInUser | null>({
    key: 'signedInUserSelector',
    get: ({ get }) => get(signedInUserAtom),
    set: ({ set, get }, newUserValue) => {
        // ... Do a bunch of stuff when a user signs in ...

        set(signedInUserAtom, newUserValue)
    }
})

So basically I use signedInUserSelector in order to set the new user.
Now, I want to have a few functions that will set the user through the selector, and use them across my components, like:

  export async function signInWithGoogleAccount() {
     const googleUser = async googleClient.signIn()
     // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(googleUser)
  }

  export async function signInWithLocalAccount(email: string, password: string) {
     const localUser = async localClient.signIn(email, password)
     // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(localUser)
  }

  export async function signOut() {
      await localClient.signOut()
      // here I need to set the user atom like:
     //  const [user, setUser] = useRecoilState(signedInUserSelector)
     // setUser(null)
  }

The problem is since these functions are not defined inside components I can't use recoil hooks (like useRecoilState to access selectors/atoms).

In the end I want to have any component to be able to do:

function SignInFormComponent() {
  return <button onClick={signInWithGoogleAccount}>Sign In</button>
}

But how can I access selectors/atoms in signInWithGoogleAccount if it is not in a component?


Solution

  • As I pointed out in another answer, you generally don't want to run into this, but if you eventually really need to update atoms outside of React Components you might give a try to Recoil Nexus.

    In the same file where you have your RecoilRoot you'll have something like:

    import React from 'react';
    import { RecoilRoot } from "recoil"
    import RecoilNexus from 'recoil-nexus'
    
    export default function App() {
      return (
        <RecoilRoot>
          <RecoilNexus/>
          
          {/* ... */}
          
        </RecoilRoot>
      );
    };
    
    
    export default App;
    

    Then, wherever you need to read/update the values:

    import yourAtom from './yourAtom'
    import { getRecoil, setRecoil } from 'recoil-nexus'
    

    Eventually you can get and set the values like this:

    // Read value
    const loading = getRecoil(loadingState)
    console.table({ loading })
    
    // Set value
    setRecoil(loadingState, true)
    

    Or, if the new state depends on the previous one, it's recommended to use an updater function to correctly batch React state updates:

    setRecoil(loadingState, loading => !loading)
    

    That's it!

    Disclaimer: I am the author of the library.


    Check this CodeSandbox for a live example.