Recoil
Recoil copied to clipboard
[SSR] Proposal: useRecoilValueAfterMount
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.
+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.