{"id":24822213,"url":"https://github.com/tigerabrodi/react-query-from-scratch","last_synced_at":"2025-04-05T17:05:18.632Z","repository":{"id":274922042,"uuid":"924051111","full_name":"tigerabrodi/react-query-from-scratch","owner":"tigerabrodi","description":"A scoped down implementation of React Query with a simplified architecture.","archived":false,"fork":false,"pushed_at":"2025-01-30T05:58:00.000Z","size":97,"stargazers_count":141,"open_issues_count":0,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-29T16:04:22.669Z","etag":null,"topics":["caching","react","react-query","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tigerabrodi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-29T10:27:21.000Z","updated_at":"2025-03-27T04:05:12.000Z","dependencies_parsed_at":"2025-01-30T06:39:18.732Z","dependency_job_id":null,"html_url":"https://github.com/tigerabrodi/react-query-from-scratch","commit_stats":null,"previous_names":["tigerabrodi/react-query-from-scratch"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigerabrodi%2Freact-query-from-scratch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigerabrodi%2Freact-query-from-scratch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigerabrodi%2Freact-query-from-scratch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tigerabrodi%2Freact-query-from-scratch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tigerabrodi","download_url":"https://codeload.github.com/tigerabrodi/react-query-from-scratch/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247369953,"owners_count":20927928,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","react","react-query","typescript"],"created_at":"2025-01-30T18:27:01.474Z","updated_at":"2025-04-05T17:05:18.594Z","avatar_url":"https://github.com/tigerabrodi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TanStack Query from scratch\n\nA scoped implementation of React Query to understand the core concepts.\n\n## Core features\n\n✅ Basic query caching  \n✅ Optimistic updates  \n✅ Error handling \u0026 rollback  \n✅ Garbage collection  \n✅ Background revalidation  \n✅ Query invalidation  \n✅ Deduplication of requests  \n✅ Stale-while-revalidate pattern  \n✅ Dependent queries  \n✅ Parallel queries\n\n## Get it up and running\n\n```bash\ngit clone https://github.com/tigerabrodi/react-query-from-scratch\npnpm install\npnpm test\n```\n\n## Architecture for this implementation\n\nThe architecture here is different from the real TanStack query. The real one uses query observers that sit between the component and the query cache. You can read how it works here: [Inside React Query](https://tkdodo.eu/blog/inside-react-query). Their architecture is obviously more complex. It's nice though, because it's easy to create adapters for different frameworks.\n\n### My architecture\n\n```mermaid\ngraph TD\n    %% Main Components\n    QueryClient[QueryClient] --\u003e QueryCache[QueryCache]\n    QueryCache --\u003e |manages| Cache[(Cache Map)]\n    QueryCache --\u003e |manages| PromisesInFlight[(Promises In Flight Map)]\n    QueryCache --\u003e |manages| Subscribers[(Subscribers Map)]\n    QueryCache --\u003e |manages| GCTimeouts[(GC Timeouts Map)]\n    QueryCache --\u003e |manages| GCQueue[(GC Queue Set)]\n\n    %% Hooks\n    useQuery --\u003e |uses| useQueryClient\n    useQuery --\u003e |uses| useSyncExternalStore\n    useQuery --\u003e |initializes| InitialStateRef[Initial State Ref]\n    useMutation --\u003e |uses| useQueryClient\n    useMutation --\u003e |manages| MutationState[Mutation State]\n\n    %% QueryClient Operations\n    QueryClient --\u003e |fetchQuery| HashQueryKey[Hash Query Key]\n    QueryClient --\u003e |invalidateQueries| QueryCache\n    QueryClient --\u003e |cancelQueries| QueryCache\n    QueryClient --\u003e |setQueryData| QueryCache\n    QueryClient --\u003e |getQueryData| QueryCache\n    QueryClient --\u003e |refetchQueries| QueryCache\n\n    %% QueryCache Core Operations\n    QueryCache --\u003e |directQuery| DirectQueryFlow[Direct Query Flow]\n    QueryCache --\u003e |backgroundQuery| BackgroundQueryFlow[Background Query Flow]\n    QueryCache --\u003e |setData| SetDataFlow[Set Data Flow]\n    QueryCache --\u003e |invalidateQuery| InvalidateFlow[Invalidate Flow]\n    QueryCache --\u003e |subscribe/unsubscribe| SubscriptionFlow[Subscription Flow]\n    QueryCache --\u003e |scheduleGC| GCFlow[GC Flow]\n\n    %% Direct Query Flow Detail\n    DirectQueryFlow --\u003e |check| PromiseInFlightCheck{Promise In Flight?}\n    PromiseInFlightCheck --\u003e|yes| ReturnExisting[Return Existing Promise]\n    PromiseInFlightCheck --\u003e|no| InitialDataCheck{Has Initial Data?}\n    InitialDataCheck --\u003e|yes| SetInitialState[Set First Success State]\n    InitialDataCheck --\u003e|no| SetLoadingState[Set Loading State]\n    SetLoadingState --\u003e ExecuteQueryFn[Execute Query Function]\n    ExecuteQueryFn --\u003e HandleResponse{Success/Error}\n    HandleResponse --\u003e|success| SetSuccessState[Set Success State]\n    HandleResponse --\u003e|error| SetErrorState[Set Error State]\n\n    %% Background Query Flow Detail\n    BackgroundQueryFlow --\u003e |check multiple conditions| BackgroundChecks{\n        1. No Promise In Flight\n        2. Entry Exists\n        3. In Success State\n        4. Is Stale\n        5. Not First Fetch Buffer\n    }\n    BackgroundChecks --\u003e|all pass| SetFetchingState[Set Fetching State]\n    SetFetchingState --\u003e ExecuteBackgroundQuery[Execute Query Function]\n    ExecuteBackgroundQuery --\u003e HandleBackgroundResponse{Success/Error}\n    HandleBackgroundResponse --\u003e|success| SetNewSuccessState[Set New Success State]\n    HandleBackgroundResponse --\u003e|error| RestorePreviousState[Restore Previous State]\n\n    %% Subscription Flow Detail\n    SubscriptionFlow --\u003e AddSubscriber[Add Subscriber to Map]\n    AddSubscriber --\u003e NotifySubscriber[Notify on State Change]\n    SubscriptionFlow --\u003e RemoveSubscriber[Remove Subscriber]\n    RemoveSubscriber --\u003e CheckGC{No Subscribers?}\n    CheckGC --\u003e|yes| TriggerGC[Schedule GC]\n\n    %% GC Flow Detail\n    GCFlow --\u003e CheckExisting[Check Existing Timeout]\n    CheckExisting --\u003e SetTimeout[Set New Timeout]\n    SetTimeout --\u003e CleanupItems[\n        1. Remove from Cache\n        2. Remove from Promises\n        3. Remove from Subscribers\n        4. Remove from GC Timeouts\n    ]\n\n    %% States\n    subgraph QueryStates\n        IdleState[Idle]\n        LoadingState[Loading]\n        FetchingState[Fetching]\n        SuccessState[Success]\n        FirstSuccessState[First Success]\n        ErrorState[Error]\n    end\n\n    %% Mutation Flow\n    subgraph MutationFlow\n        OnMutate[onMutate] --\u003e CancelQueries\n        CancelQueries --\u003e OptimisticUpdate[Set Optimistic Data]\n        OptimisticUpdate --\u003e ExecuteMutation[Execute Mutation]\n        ExecuteMutation --\u003e HandleMutationResult{Success/Error}\n        HandleMutationResult --\u003e|success| OnSuccess[onSuccess]\n        HandleMutationResult --\u003e|error| OnError[onError + Rollback]\n        OnSuccess --\u003e OnSettled[onSettled]\n        OnError --\u003e OnSettled\n        OnSettled --\u003e InvalidateRelatedQueries[Invalidate Queries]\n    end\n\n    %% Formatting\n    classDef core fill:#f9f,stroke:#333,stroke-width:2px\n    classDef state fill:#bbf,stroke:#333,stroke-width:1px\n    classDef flow fill:#dfd,stroke:#333,stroke-width:1px\n    class QueryClient,QueryCache core\n    class IdleState,LoadingState,FetchingState,SuccessState,FirstSuccessState,ErrorState state\n    class DirectQueryFlow,BackgroundQueryFlow,SetDataFlow,InvalidateFlow,SubscriptionFlow,GCFlow flow\n```\n\n# How they handle race conditions\n\nOne interesting thing is how the real TanStack Query handles race conditions. They use mutation scopes with queues, where only one mutation can be active per scope. That's how they prevent race conditions. See their [mutationCache.ts](https://github.com/TanStack/query/blob/main/packages/query-core/src/mutationCache.ts).\n\n# Features missing\n\nThere is a lot of things that aren't implemented here that from the full TanStack Query implementation.\n\nQuery features:\n\n- Prefetching queries (shouldn't be too tricky in hindsight with what I've done here)\n- Query retries and retry config (we'd need to retry with exponential backoff)\n- Window focus refetching (need to listen to window focus events)\n- Network status refetching (need to listen to network status events)\n- Polling/refetchInterval (something you'd configure in the query options)\n- Infinite queries (for pagination/infinite scroll)\n- Suspense queries\n\nFor `useSuspenseQuery`, we'd need to throw the promise. I dug into the source code before writing this, but their [useSuspenseQuery](https://github.com/TanStack/query/blob/main/packages/react-query/src/useSuspenseQuery.ts) hook is just a wrapper around `useBaseQuery` which they use. However, suspense is enabled. If it should suspense, they throw the fetch here: [useBaseQuery.ts#L116](https://github.com/TanStack/query/blob/main/packages/react-query/src/useBaseQuery.ts#L116). Very cool.\n\nMutation features:\n\n- Mutation retries\n- Race condition handling\n- Mutation queues\n- Mutation keys/scoping\n\n---\n\nFor learning purposes, this implementation focuses on the core concepts while leaving out more advanced features.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftigerabrodi%2Freact-query-from-scratch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftigerabrodi%2Freact-query-from-scratch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftigerabrodi%2Freact-query-from-scratch/lists"}