diff --git a/src/src.ts b/src/src.ts index b9fe489..f2dc722 100644 --- a/src/src.ts +++ b/src/src.ts @@ -18,6 +18,27 @@ import {wait, generateUUID} from "./util"; import {setupRetries} from "./retry"; import {useShallow} from "zustand/react/shallow"; import {events} from "./events"; +import {useEffect} from "react"; + +const mountedQueriesTracker = {} as Record>; + +const registerQuery = (queryId: string) => { + mountedQueriesTracker[queryId] = new Set(); +}; + +const mountQuery = (hookId: string, queryId: string) => { + mountedQueriesTracker[queryId].add(hookId); +}; + +const unmountQueries = (hookId: string) => { + Object + .keys(mountedQueriesTracker) + .forEach(queryId => { mountedQueriesTracker[queryId].delete(hookId); }); +}; + +const isQueryMounted = (queryId: string) => + mountedQueriesTracker[queryId].size > 0; + export const isEffect = (v: any): v is Effect => v && (v as Effect).__type === "Effect"; @@ -316,6 +337,7 @@ export function query(): Query { withValue: (value: R): Query => setSync(q, value, /*error*/undefined, {__updateStore: false}) }; + registerQuery(q.__id); return q; } @@ -341,6 +363,28 @@ const select = (selector: (s: T) => QueryOrEffect) } } +function useTrackMountedQueries(selectors: { [K in keyof R]: ((state: T) => Query) | {selector: ((state: T) => Query), opts: UseBoundAsyncStoreOptions} }, store: UseBoundStore>): void; +function useTrackMountedQueries(selector: (state: T) => Query, store: UseBoundStore>): void; +function useTrackMountedQueries(selector: (state: T) => Effect, store: UseBoundStore>): void; +function useTrackMountedQueries(selector: any, store: UseBoundStore>): void { + useEffect(() => { + const hookId = generateUUID(globalOptions); + const state = store.getState(); + if (Array.isArray(selector)) { + selector + .map(s => typeof s === "function" ? s : s.selector) + .map(s => s(state)) + .forEach((q: Query) => { mountQuery(hookId, q.__id); }); + } else { + const query = selector(state); + if (isQuery(query)) { + mountQuery(hookId, query.__id); + } + } + return () => { unmountQueries(hookId); }; + }, []); +} + /** * React hook to retrieve queries or effects from your store. This hook will automatically trigger Suspense when a query is loading. * @param store - Your Zustand store @@ -351,6 +395,7 @@ const withSuspenseHook = (store: UseBoundStore>): function useBoundAsyncStore(selector: (state: T) => Query, opts?: UseBoundAsyncStoreOptions): R; function useBoundAsyncStore(selector: (state: T) => Effect): (() => Promise); function useBoundAsyncStore(s: any, o?: any): any { + useTrackMountedQueries(s, store); function single(selector: (state: T) => Query | Effect, opts?: UseBoundAsyncStoreOptions): R | (() => Promise) { const _opts = opts || {}; const theSelector = select(selector); @@ -402,6 +447,7 @@ const withoutSuspenseHook = (store: UseBoundStore> function useBoundAsyncStoreWithoutSuspense(selector: (state: T) => Query, opts?: UseBoundAsyncStoreOptions): QueryValue; function useBoundAsyncStoreWithoutSuspense(selector: (state: T) => Effect): (() => Promise); function useBoundAsyncStoreWithoutSuspense(s: any, o?: any): any { + useTrackMountedQueries(s, store); function single(selector: (state: T) => Query | Effect, opts?: UseBoundAsyncStoreOptions): QueryValue | (() => Promise) { const _opts = opts || {}; const theSelector = select(selector); @@ -466,10 +512,13 @@ const subscribe = (store: UseBoundStore>) => { const currDeps = current.__deps(state); const prevDeps = prev.__deps(prevState); const dependencyChange = !equals(currDeps, prevDeps); - if (dependencyChange && current.__lazy) { - return current.markStale(); + const isMounted = isQueryMounted(current.__id); + if (dependencyChange && isMounted) { + return current.trigger(); } else if (dependencyChange && !current.__lazy) { return current.trigger(); + } else if (dependencyChange && current.__lazy) { + return current.markStale(); } } });