diff --git a/compat/src/index.js b/compat/src/index.js index b7312f00d3..49c67a7aa8 100644 --- a/compat/src/index.js +++ b/compat/src/index.js @@ -133,24 +133,39 @@ export function useTransition() { // styles/... before it attaches export const useInsertionEffect = useLayoutEffect; +/** + * This is taken from https://github.com/facebook/react/blob/main/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js#L84 + * on a high level this cuts out the warnings, ... and attempts a smaller implementation + */ export function useSyncExternalStore(subscribe, getSnapshot) { - const [state, setState] = useState(getSnapshot); - const value = getSnapshot(); + const [{ _instance }, forceUpdate] = useState({ + _instance: { _value: value, _getSnapshot: getSnapshot } + }); + useLayoutEffect(() => { - if (value !== state) { - setState(() => value); + _instance._value = value; + _instance._getSnapshot = getSnapshot; + + if (_instance._value !== getSnapshot()) { + forceUpdate({ _instance }); } }, [subscribe, value, getSnapshot]); useEffect(() => { + if (_instance._value !== _instance._getSnapshot()) { + forceUpdate({ _instance }); + } + return subscribe(() => { - setState(() => getSnapshot()); + if (_instance._value !== _instance._getSnapshot()) { + forceUpdate({ _instance }); + } }); - }, [subscribe, getSnapshot]); + }, [subscribe]); - return state; + return value; } export * from 'preact/hooks'; diff --git a/compat/test/browser/hooks.test.js b/compat/test/browser/hooks.test.js index 825cb07509..3255704756 100644 --- a/compat/test/browser/hooks.test.js +++ b/compat/test/browser/hooks.test.js @@ -93,7 +93,7 @@ describe('React-18-hooks', () => { }); expect(scratch.innerHTML).to.equal('

hello world

'); expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledTwice; + expect(getSnapshot).to.be.calledThrice; }); it('subscribes and rerenders when called', () => { @@ -121,7 +121,7 @@ describe('React-18-hooks', () => { }); expect(scratch.innerHTML).to.equal('

hello world

'); expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledTwice; + expect(getSnapshot).to.be.calledThrice; called = true; flush(); @@ -154,7 +154,7 @@ describe('React-18-hooks', () => { }); expect(scratch.innerHTML).to.equal('

value: 0

'); expect(subscribe).to.be.calledOnce; - expect(getSnapshot).to.be.calledTwice; + expect(getSnapshot).to.be.calledThrice; flush(); rerender(); @@ -162,6 +162,34 @@ describe('React-18-hooks', () => { expect(scratch.innerHTML).to.equal('

value: 0

'); }); + it('should work with changing getSnapshot', () => { + let flush; + const subscribe = sinon.spy(cb => { + flush = cb; + return () => {}; + }); + + let i = 0; + const App = () => { + const value = useSyncExternalStore(subscribe, () => { + return i; + }); + return

value: {value}

; + }; + + act(() => { + render(, scratch); + }); + expect(scratch.innerHTML).to.equal('

value: 0

'); + expect(subscribe).to.be.calledOnce; + + i++; + flush(); + rerender(); + + expect(scratch.innerHTML).to.equal('

value: 1

'); + }); + it('works with useCallback', () => { let toggle; const App = () => {