From d2c029b5b97fd93cecd621d80e03189e772277ea Mon Sep 17 00:00:00 2001 From: vision Date: Fri, 7 Jun 2024 15:21:29 +0800 Subject: [PATCH 1/4] fix: should add subscribe after hotload --- src/hooks.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hooks.ts b/src/hooks.ts index 41e0ea2..00bc719 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -161,6 +161,8 @@ export function useSelector(...selectors: Rs): MapSelec if (lastStore.current?.store !== store) { lastSelector.current = defaultSelectorRef; lastStore.current?.disposer(); + } + useEffect(() => { lastStore.current = { store, updated: false, @@ -200,15 +202,15 @@ export function useSelector(...selectors: Rs): MapSelec } }), }; - } - useEffect(() => () => lastStore.current?.disposer(), []); - if (lastStore.current.error) { + return () => lastStore.current?.disposer(); + }, [store]); + if (lastStore.current?.error) { const error = lastStore.current.error; lastStore.current.error = void 0; throw error; } let selectedState: any; - if (lastStore.current.updated) { + if (lastStore.current?.updated) { lastStore.current.updated = false; selectedState = lastSelector.current.results; } else { From 7fbeccd7cd50595091917d1e473fe97ca6b889cd Mon Sep 17 00:00:00 2001 From: vision Date: Fri, 7 Jun 2024 18:12:08 +0800 Subject: [PATCH 2/4] fix: should add subscribe after hotload --- src/hooks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hooks.ts b/src/hooks.ts index 00bc719..b004de0 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -3,7 +3,7 @@ * @author acrazing */ -import { useContext, useDebugValue, useEffect, useReducer, useRef } from 'react'; +import { useContext, useDebugValue, useLayoutEffect, useReducer, useRef } from 'react'; import { __Context } from './context'; import { Selector } from './selector'; import { Dispatch, Selectable, Snapshot, Store } from './store'; @@ -162,7 +162,7 @@ export function useSelector(...selectors: Rs): MapSelec lastSelector.current = defaultSelectorRef; lastStore.current?.disposer(); } - useEffect(() => { + useLayoutEffect(() => { lastStore.current = { store, updated: false, From 592c5fab59c04b8e1e80ffd47fafe182157ff1a9 Mon Sep 17 00:00:00 2001 From: vision Date: Wed, 19 Jun 2024 17:43:25 +0800 Subject: [PATCH 3/4] fix: should add subscribe after hotload --- package.json | 1 + src/hooks.ts | 103 ++++++++++++++++++++++++++++++++------------------- 2 files changed, 65 insertions(+), 39 deletions(-) diff --git a/package.json b/package.json index ca96fa9..fbeb120 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ ], "scripts": { "clean": "rm -rf dist", + "dev": "rollup -w --config rollup.config.js", "bundle": "rollup --config rollup.config.js", "build": "run-s clean bundle", "test": "jest", diff --git a/src/hooks.ts b/src/hooks.ts index b004de0..e42f090 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -3,12 +3,19 @@ * @author acrazing */ -import { useContext, useDebugValue, useLayoutEffect, useReducer, useRef } from 'react'; +import { useContext, useDebugValue, useLayoutEffect, useEffect, useReducer, useRef } from 'react'; import { __Context } from './context'; import { Selector } from './selector'; import { Dispatch, Selectable, Snapshot, Store } from './store'; import { arrayEqual, strictEqual } from './utils'; +export const useIsomorphicLayoutEffect = + typeof window !== 'undefined' && + typeof window.document !== 'undefined' && + typeof window.document.createElement !== 'undefined' + ? useLayoutEffect + : useEffect; + /** * use context's store * @@ -158,11 +165,58 @@ export function useSelector(...selectors: Rs): MapSelec const store = useStore(); const lastSelector = useRef(defaultSelectorRef); const lastStore = useRef(); + const lastState = useRef([]); + if (lastStore.current?.store !== store) { lastSelector.current = defaultSelectorRef; - lastStore.current?.disposer(); } - useLayoutEffect(() => { + + if (lastStore.current?.error) { + const error = lastStore.current.error; + lastStore.current.error = void 0; + throw error; + } + + const resolveState = () => { + if (lastStore.current?.updated) { + lastStore.current.updated = false; + return lastSelector.current.results; + } else { + if (lastSelector.current === defaultSelectorRef) { + lastSelector.current = { selectors: [], deps: [], snapshots: [], results: [] }; + } + // updates from outside + const { selectors: oldSelectors, deps, snapshots, results } = lastSelector.current; + for (let i = 0; i < selectors.length; i++) { + const old = oldSelectors[i]; + const newly = selectors[i]; + if (typeof newly === 'object') { + results[i] = store.select(newly); + oldSelectors[i] = newly; + } else { + const newDeps = selectorChanged(old, newly, snapshots[i], store, deps[i]); + if (newDeps) { + snapshots[i] = void 0; + const newSnapshot: Snapshot = {}; + results[i] = store.select(newly, newSnapshot); + deps[i] = newDeps === true ? void 0 : newDeps; + snapshots[i] = newSnapshot; + oldSelectors[i] = newly; + } + } + } + results.length = selectors.length; + return results; + } + }; + + let selectedState: any = resolveState(); + + useIsomorphicLayoutEffect(() => { + lastState.current = [...selectedState]; + }); + + useIsomorphicLayoutEffect(() => { lastStore.current = { store, updated: false, @@ -202,44 +256,15 @@ export function useSelector(...selectors: Rs): MapSelec } }), }; + + // if something change between render and the effect. eg. dispatch when render + if (!arrayEqual(lastState.current, resolveState())) { + update(); + } + return () => lastStore.current?.disposer(); }, [store]); - if (lastStore.current?.error) { - const error = lastStore.current.error; - lastStore.current.error = void 0; - throw error; - } - let selectedState: any; - if (lastStore.current?.updated) { - lastStore.current.updated = false; - selectedState = lastSelector.current.results; - } else { - if (lastSelector.current === defaultSelectorRef) { - lastSelector.current = { selectors: [], deps: [], snapshots: [], results: [] }; - } - // updates from outside - const { selectors: oldSelectors, deps, snapshots, results } = lastSelector.current; - for (let i = 0; i < selectors.length; i++) { - const old = oldSelectors[i]; - const newly = selectors[i]; - if (typeof newly === 'object') { - results[i] = store.select(newly); - oldSelectors[i] = newly; - } else { - const newDeps = selectorChanged(old, newly, snapshots[i], store, deps[i]); - if (newDeps) { - snapshots[i] = void 0; - const newSnapshot: Snapshot = {}; - results[i] = store.select(newly, newSnapshot); - deps[i] = newDeps === true ? void 0 : newDeps; - snapshots[i] = newSnapshot; - oldSelectors[i] = newly; - } - } - } - results.length = selectors.length; - selectedState = results; - } + // TODO: print friendly with selector names useDebugValue(selectedState, (value: any[]) => { return value.reduce((map, value, index) => { From f956b2c99334a24948db15e21dd40c69034890ad Mon Sep 17 00:00:00 2001 From: vision Date: Fri, 13 Sep 2024 19:38:29 +0800 Subject: [PATCH 4/4] fix: should add subscribe after hotload --- src/hooks.spec.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/hooks.spec.tsx b/src/hooks.spec.tsx index 08350dd..d062e57 100644 --- a/src/hooks.spec.tsx +++ b/src/hooks.spec.tsx @@ -80,12 +80,17 @@ describe('useSelector', () => { const defaultSelector = selector(defaultFn); const strictSelector = selector(strictFn, (select, times) => [select(testBox).count, times]); const inlineFn = fn((select: Select) => select(testBox).count); - const expectCalled = (isDefault: boolean, isStrict: boolean, isInline: boolean) => { + const expectCalled = ( + isDefault: boolean, + isStrict: boolean, + isInline: boolean, + first?: boolean, + ) => { expect(defaultFn).toBeCalledTimes(isDefault ? 1 : 0); defaultFn.mockClear(); - expect(strictFn).toBeCalledTimes(isStrict ? 1 : 0); + expect(strictFn).toBeCalledTimes(isStrict ? (first ? 2 : 1) : 0); strictFn.mockClear(); - expect(inlineFn).toBeCalledTimes(isInline ? 1 : 0); + expect(inlineFn).toBeCalledTimes(isInline ? (first ? 2 : 1) : 0); inlineFn.mockClear(); }; const { result, dispatch, rerender, waitForNextUpdate } = renderUseSelector( @@ -99,7 +104,7 @@ describe('useSelector', () => { { count: 1, test: { count: 1, greets: ['PRELOAD'] } }, { multiply: 1 }, ); - expectCalled(true, true, true); + expectCalled(true, true, true, true); expect(result.current).toEqual([1, 1, 1, 1]); dispatch(addGreet('HELLO')); await waitForNextUpdate();