Recoil: onSet() handler in effect not triggered when atom initialized via RecoilRoot initializeState or snapshot

RecoilRoot using initializeState:

      <RecoilRoot
        initializeState={({ set }) => set(domainState, props.savedDomainState)}
      >
        <AppRoot root={props.root} />
      </RecoilRoot>

The Atom using an effect:

const domainState = atom({
  key: "domainState",
  default: null,
  effects_UNSTABLE: [syncDomainStorage],
});

The function: syncDomainStorage is only triggered on initial render when I use initializeState, only if I remove initializeState the side-effect function is called when the atom is updated.

Any updates to domainState atom should be triggering the effect, which is not the behavior observed when used along with RecoilRoot initializeState

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 8
  • Comments: 19 (6 by maintainers)

Most upvoted comments

Hey! Thanks for reply @drarmstr .

I’m using cookies from request header to set Atoms state on initial Server-side render. And I want effects_UNSTABLE onSet() to mutate cookie client-side when state changes.

My problem is probably framework-specific since nextjs framework gets ServerSide parameters and passes them to components like this <App {...serversideprops}></App> so initializeState is very helpful. And I couldn’t figure out how to pass current cookie header from request to atom effects_UNSTABLE setSelf 😦

Would’ve been perfect combo to have peristent state in cookies if only onSet() in effects_UNSTABLE could be used with initialState. I’m just using regular useEffect to set a cookie after changing atom state for now.

Added example here. _app.js - here I pass cookies to initializeState on initial server-side render. (Breaks setSelf()) index.js - example of current behaviour.

Should be fixed with #1511 and tested with #1519 for 0.6 release.

Note you’ll also be able to initialize atoms using props with the upcoming recoil-sync library and effects instead of initializeState. (https://github.com/facebookexperimental/Recoil/pull/1462/files)

Yep, provided workarounds won’t work for example with SSR, where I want to initialize with initializeState prop on inital SSR render, but keep the effects_UNSTABLE onSet() callback functionality.

Is this issue fixed?

I’ve got an atomEffect which tracks the “history” of an atom by pushing into an array each onSet but it doesn’t update when changed within the RecoilRoot initializeState

(I’m using Recoil 0.7.5)

Bump! I’m in the same boat as @Noo8lord - I have a NextJS SSR app in production which relies on session tokens to understand who the user is, fetch data on the server, and initialize recoil state in recoil root. Was hoping that I could initialize atoms from the server as I’ve been doing, and then use the onSet() atom effect to manage subsequent recoil state persistence and side effects, but it doesn’t seem to be possible.

As mentioned somewhere above, the workaround of enabling effects by dropping initializeState in favor of asynchronous setSelf effects for each atom isn’t useful in my case because it seems to negate the utility of SSR, and because I can’t give the atom effects the context they need to fetch the right data on the server.

Appreciate any help / follow up and happy to provide more info.

@drarmstr I’ve added a PR https://github.com/facebookexperimental/Recoil/pull/828 for an update docs with the asynchronous / promise examples we discussed. I would have liked that when looking through the docs, so maybe other would as well.

Let me know if there’s something badly explained.

Note that Atom Effect functions are themselves not async and should not return a Promise. You can schedule async calls to setSelf() in them, but that will only set the atom value asynchronously after initial render, not initialize the atom state for initial render (which would use the atom’s default value). If you need to get the initial state asynchronously you could synchronously call setSelf() with an async Promise. This will cause the atom to be initialized in a pending state that will leverage Suspense for the initial render. The approach you want is up to you, but be aware of the differences.

const loadPersisted = <T>({ key, setSelf }) => {
  setSelf(localForage.getItem(key).then(JSON.parse));
};