From 6eef4bee6299544c7ac86e5e864212d6de9397af Mon Sep 17 00:00:00 2001 From: ahmedx33 Date: Thu, 10 Oct 2024 09:01:28 +0300 Subject: [PATCH] feat: added isStale - isFetched - isSuccess - error - fetchStatus --- .changeset/green-singers-walk.md | 5 ++ .github/workflows/publish.yml | 14 +++- .gitignore | 1 + packages/duck-query/src/core/useQuery.ts | 91 +++++++++++++----------- 4 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 .changeset/green-singers-walk.md diff --git a/.changeset/green-singers-walk.md b/.changeset/green-singers-walk.md new file mode 100644 index 0000000..b8ad2dd --- /dev/null +++ b/.changeset/green-singers-walk.md @@ -0,0 +1,5 @@ +--- +"duck-query": minor +--- + +added isStale - isFetched - isSuccess - error - fetchStatus diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1675597..13a3b88 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: - name: Install dependencies run: npm install - # if i have tests i will uncomment it + # Uncomment if you have tests # - name: Run tests # run: npm test @@ -35,3 +35,15 @@ jobs: run: | npx changeset version # Version packages based on changesets npm publish # Publish the packages to npm + + - name: Create GitHub Release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHU_TOKEN }} + with: + tag_name: v${{ steps.set_version.outputs.new_version }} + release_name: Release v${{ steps.set_version.outputs.new_version }} + body: | + New release based on changesets. + draft: false + prerelease: false diff --git a/.gitignore b/.gitignore index a547bf3..f9854f9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ dist-ssr .vscode/* !.vscode/extensions.json .idea +.note .DS_Store *.suo *.ntvs* diff --git a/packages/duck-query/src/core/useQuery.ts b/packages/duck-query/src/core/useQuery.ts index 18d77cb..af071c6 100644 --- a/packages/duck-query/src/core/useQuery.ts +++ b/packages/duck-query/src/core/useQuery.ts @@ -7,6 +7,7 @@ type Query = { refetchInterval?: number; refetchOnWindowFocus?: boolean; gcTime?: number; + enabled: boolean }; type CacheEntry = { @@ -14,16 +15,24 @@ type CacheEntry = { timestamp: number; }; +type FetchStatus = "fetching" | "idle" | "paused" + type QueryOptions = { data: T | null; isLoading: boolean; isError: boolean; + error: any; refetch: () => void; + isStale: boolean + isFetched: boolean + isSuccess: boolean + fetchStatus: FetchStatus }; -// global cache cuz when i add it inside the hook it will rebuild everytime cuz the component destroys every hook when unmount const cache: Record> = {}; +// TODO make paused after doing retries + export function useQueryNew({ queryKey, queryFn, @@ -31,114 +40,112 @@ export function useQueryNew({ refetchInterval = 0, refetchOnWindowFocus = false, gcTime, + enabled = true }: Query): QueryOptions { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); + const [error, setError] = useState(null) + const [isStale, setIsStale] = useState(false) + const [isFetched, setIsFetched] = useState(false) + const [isSuccess, setIsSuccess] = useState(false) + const [fetchStatus, setFetchStatus] = useState("idle") const key = JSON.stringify(queryKey); const intervalRef = useRef(null); const staleCheckRef = useRef(null); const checkStale = () => { const cachedData = cache[key]; + if (cachedData && Date.now() - cachedData.timestamp > staleTime) { fetcher(); } }; const clearUnusedCache = (key: string, gcTime: number) => { - if (cache[key]) { - if (Date.now() - cache[key].timestamp > gcTime) { - delete cache[key]; - } + if (gcTime && cache[key] && Date.now() - cache[key].timestamp > gcTime) { + delete cache[key]; } }; const fetcher = async () => { + if (!enabled) return; setIsLoading(true); + setFetchStatus("fetching") setIsError(false); try { const result = await queryFn(); - setIsLoading(false); - cache[key] = { - data: result, - timestamp: Date.now(), - }; + cache[key] = { data: result, timestamp: Date.now() }; setData(result); + setIsSuccess(true) } catch (err) { + console.log(err) setIsError(true); - throw new Error(err as string); + setIsSuccess(false) + setError(err) + setFetchStatus("fetching") } finally { setIsLoading(false); + setIsFetched(true) + setFetchStatus("idle") } }; - const refetch = () => { - fetcher(); - }; + const refetch = () => fetcher(); useEffect(() => { if (refetchOnWindowFocus) { const handleVisibilityChange = () => { const cachedData = cache[key]; const isStale = !cachedData || (Date.now() - cachedData.timestamp > staleTime); - if (document.visibilityState === "visible" && isStale) { fetcher(); } }; - document.addEventListener("visibilitychange", handleVisibilityChange); - - return () => { - document.removeEventListener("visibilitychange", handleVisibilityChange); - }; + return () => document.removeEventListener("visibilitychange", handleVisibilityChange); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [key, staleTime, queryFn, refetchOnWindowFocus]); + }, [key, staleTime, refetchOnWindowFocus]); useEffect(() => { const cachedData = cache[key]; const isStale = cachedData && Date.now() - cachedData.timestamp > staleTime; - clearUnusedCache(key, gcTime as number); + if (gcTime) clearUnusedCache(key, gcTime); - if (staleTime > 0 && staleCheckRef.current === null) { - staleCheckRef.current = window.setInterval(() => { - checkStale(); - }, staleTime); + if (staleTime > 0 && !staleCheckRef.current) { + staleCheckRef.current = window.setInterval(checkStale, staleTime); } - if (refetchInterval !== 0 && intervalRef.current === null) { - intervalRef.current = window.setInterval(() => { - fetcher(); - }, refetchInterval); + if (refetchInterval > 0 && !intervalRef.current) { + intervalRef.current = window.setInterval(fetcher, refetchInterval); } if (cachedData && !isStale) { setData(cachedData.data as T); - return; + setIsStale(true) + } else { + fetcher(); + setIsStale(false) } - fetcher(); - return () => { - if (intervalRef.current !== null) { - clearInterval(intervalRef.current); - } - if (staleCheckRef.current !== null) { - clearInterval(staleCheckRef.current); - } + if (intervalRef.current) clearInterval(intervalRef.current); + if (staleCheckRef.current) clearInterval(staleCheckRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [key, staleTime, refetchInterval, gcTime]); - - return { data, isLoading, isError, refetch, + error, + isStale, + isFetched, + isSuccess, + fetchStatus }; -} +} \ No newline at end of file