facebookexperimental/Recoil

[SSR] Proposal: useRecoilValueAfterMount

Open

#726 opened on Nov 8, 2020

View on GitHub
 (1 comment) (16 reactions) (0 assignees)JavaScript (19,428 stars) (1,151 forks)batch import
help wanted

Description

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.

Contributor guide