I think the only way (at least as of a few months ago) is a sort of hack where you include a non-rendering component that uses the recoil hooks and exports the provided functions from them.
See: https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777249693
Below is the file from my own project that achieves this, heavily based on that link above. All you need to do is put <RecoilExternalStatePortal />
anywhere in your application tree that is guaranteed to always render.
This seems like an omission in the Recoil API, IMHO.
import React from 'react'
import { Loadable, RecoilState, RecoilValue, useRecoilCallback, useRecoilTransactionObserver_UNSTABLE } from 'recoil'
/**
* Returns a Recoil state value, from anywhere in the app.
*
* Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
* <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
* Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
*
* @example const lastCreatedUser = getRecoilExternal(lastCreatedUserState);
*/
export function getRecoilState<T>(recoilValue: RecoilValue<T>): T {
return getRecoilLoadable(recoilValue).getValue()
}
/** The `getLoadable` function from recoil. This shouldn't be used directly. */
let getRecoilLoadable: <T>(recoilValue: RecoilValue<T>) => Loadable<T> = () => null as any
/**
* Sets a Recoil state value, from anywhere in the app.
*
* Can be used outside of the React tree (outside a React component), such as in utility scripts, etc.
*
* <RecoilExternalStatePortal> must have been previously loaded in the React tree, or it won't work.
* Initialized as a dummy function "() => null", it's reference is updated to a proper Recoil state mutator when RecoilExternalStatePortal is loaded.
*
* @example setRecoilExternalState(lastCreatedUserState, newUser)
*/
export let setRecoilState: <T>(recoilState: RecoilState<T>, valOrUpdater: ((currVal: T) => T) | T) => void = () =>
null as any
/**
* Utility component allowing to use the Recoil state outside of a React component.
*
* It must be loaded in the _app file, inside the <RecoilRoot> component.
* Once it's been loaded in the React tree, it allows using setRecoilExternalState and getRecoilExternalLoadable from anywhere in the app.
*
* @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777300212
* @see https://github.com/facebookexperimental/Recoil/issues/289#issuecomment-777305884
* @see https://recoiljs.org/docs/api-reference/core/Loadable/
*/
export function RecoilExternalStatePortal() {
// We need to update the getRecoilExternalLoadable every time there's a new snapshot
// Otherwise we will load old values from when the component was mounted
useRecoilTransactionObserver_UNSTABLE(({ snapshot }) => {
getRecoilLoadable = snapshot.getLoadable
})
// We only need to assign setRecoilExternalState once because it's not temporally dependent like "get" is
useRecoilCallback(({ set }) => {
setRecoilState = set
return async () => {
// no-op
}
})()
return <></>
}