Skip to content
This repository has been archived by the owner on Jan 1, 2025. It is now read-only.

Commit

Permalink
Workaround nested React renderers without useSyncExternalStore() supp…
Browse files Browse the repository at this point in the history
…ort (#2001)

Summary:
Pull Request resolved: #2001

Recoil will attemp to detect if `useSyncExternalStore()` is supported before calling it.  However, sometimes the host environment supports it but creates additional React renderers, such as with `react-three-fiber`, which do not.  Since React goes through a proxy dispatcher we can't simply check if `useSyncExternalStore()` is defined.  Thus, this workaround will catch the situation and fallback to using `useState()` and `useEffect()`.

Differential Revision: D39329856

fbshipit-source-id: 564cd38392902fbd56c8a1b10cb99450bfe282a4
  • Loading branch information
drarmstr authored and facebook-github-bot committed Sep 7, 2022
1 parent 528c205 commit 2e5126c
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-recoil.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
## UPCOMING
**_Add new changes here as they land_**

- Workaround for React 18 environments with nested renderers that don't support `useSyncExternalStore()` (#2001)

## 0.7.5 (2022-08-11)

- Fix useRecoilSnapshot() with React's Fast Refresh during development (#1891)
Expand Down
1 change: 1 addition & 0 deletions packages/recoil/core/Recoil_ReactMode.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
* @format
* @oncall recoil
*/

'use strict';

const React = require('react');
Expand Down
23 changes: 22 additions & 1 deletion packages/recoil/hooks/Recoil_Hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ function useRecoilInterface_DEPRECATED(): RecoilInterface {

const recoilComponentGetRecoilValueCount_FOR_TESTING = {current: 0};

function useRecoilValueLoadable_SYNC_EXTERNAL_STORE<T>(
function useRecoilValueLoadable_SYNC_EXTERNAL_STORE_IMPL<T>(
recoilValue: RecoilValue<T>,
): Loadable<T> {
const storeRef = useStoreRef();
Expand Down Expand Up @@ -579,6 +579,27 @@ function useRecoilValueLoadable_LEGACY<T>(
return loadable;
}

// Recoil will attemp to detect if `useSyncExternalStore()` is supported in
// Recoil_ReactMode.js before calling it. However, sometimes the host
// environment supports it but creates additional React renderers, such as with
// `react-three-fiber`, which do not. Since React goes through a proxy
// dispatcher we can't simply check if `useSyncExternalStore()` is defined.
// Thus, this workaround will catch the situation and fallback to using
// just `useState()` and `useEffect()`.
function useRecoilValueLoadable_SYNC_EXTERNAL_STORE<T>(
recoilValue: RecoilValue<T>,
): Loadable<T> {
try {
return useRecoilValueLoadable_SYNC_EXTERNAL_STORE_IMPL(recoilValue);
} catch (e) {
if (e.message.includes('useSyncExternalStore is not a function')) {
// eslint-disable-next-line fb-www/react-hooks
return useRecoilValueLoadable_TRANSITION_SUPPORT(recoilValue);
}
throw e;
}
}

/**
Like useRecoilValue(), but either returns the value if available or
just undefined if not available for any reason, such as pending or error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
* @format
* @oncall recoil
*/

'use strict';

import type {StoreID} from '../../core/Recoil_Keys';
import type {MutableSnapshot} from 'Recoil_Snapshot';
import type {Node} from 'react';
Expand Down Expand Up @@ -56,7 +58,7 @@ function NestedReactRoot({children}: $TEMPORARY$object<{children: Node}>) {
<RecoilBridge>{children}</RecoilBridge>,
ref.current,
);
}, [children]);
}, [RecoilBridge, children]);

return <div ref={ref} />;
}
Expand Down

0 comments on commit 2e5126c

Please sign in to comment.