Recoil icon indicating copy to clipboard operation
Recoil copied to clipboard

[SSR] Proposal: useRecoilValueAfterMount

Open EqualMa opened this issue 4 years ago • 1 comments

When working with ssr, state management may cause issues when we want to initialize atoms from localStorage or something else.

When I'm trying out the local-storage-persistence example with next.js, it fails to rehydrate in the browser side. Then I realized the reason is this atom is initialized with different values:

  • On server side, the atom is initialized with defaultValue as localStorage is not available.
  • On browser side, the atom is intialized with the realValue stored before.

I think of in most cases like this, in fact we don't want it to be generated on server side. In other words, we just want use this atom and its effects in client side; on server, we just need an initialized value.

Thus, instead of making the components which use this atom client only. I write a useRecoilValueAfterMount hook which will check whether is in a mounted component:

  • if not mounted, which means this hook may be executed in server side or client side, and should return the same valueBeforeMount in case rehydate fails.
  • if mounted (which also ensures is in client side), return the realValue get from the atom

The implementation is very simple: (the following code is in typescript)

function useComponentDidMount() {
  const [componentDidMount, setComponentDidMount] = useState(false);
  useEffect(() => {
    setComponentDidMount(true);
  }, []);

  return componentDidMount;
}

export function useRecoilValueAfterMount<T>(
  recoilValue: RecoilValue<T>,
  valueBeforeMount: T,
) {
  const didMount = useComponentDidMount();
  const realValue = useRecoilValue(recoilValue);

  return didMount ? realValue : valueBeforeMount;
}

Then instead of

function CurrentUserID() {
  // this may fail the rehydrate !
  const id = useRecoilValue(currentUserIDState);
  return <div>{id}</div>
}

we can write:

function CurrentUserID() {
  // on ssr generated html, id will be null
  // and will correctly become the real value stored in localStorage after mounted in client side
  const id = useRecoilValueAfterMount(currentUserIDState, null);
  return <div>{id}</div>
}

I wonder whether this utility function needs to be added to this package. It's simple but really useful.


Further more, in most cases, valueBeforeMount is the default value of this atom. Thus, if we can get defaultValue of an atom #425 , this param can be optional and defaults to this atom's default value.

EqualMa avatar Nov 08 '20 21:11 EqualMa

+1 for this. This is a pretty common use case for us, and at the moment, we either need to switch to a loadable, which means more code in the components that consume that atom, or we need to just live with a flash of content.

jdesherlia avatar Mar 22 '21 23:03 jdesherlia