Migrate to TanStack Query for Data Fetching and Caching#27
Conversation
- Refactor useCollections and the closely related useCollection hooks to use TanStack Query for caching and fetching - Improve error propagation, keep original loading states - Update StacApiProvider to manage QueryClient and support custom clients - Clarify peer dependency requirements for @tanstack/react-query in README and docs/react-query-setup.md - Update ESLint config and package.json for new dependencies - Minor fixes to context and tests for compatibility
- Expose QueryClient on window for integration with TanStack Query DevTools (browser extension) - Documented alternative: TanStack Query Devtools floating/embedded component (see https://tanstack.com/query/latest/docs/framework/react/devtools) - Chose browser extension to keep project dependencies clean and offload devtools responsibility to the developer
- Refactor useItem to use TanStack Query for caching and fetching - Improve error propagation, keep original loading states
Adds an ItemDetails component to the example app, showing how to use the useItem hook to fetch and display STAC item details. Improves documentation for consumers of stac-react.
- Refactor useStacApi to use TanStack Query for fetching and caching - Add QueryClientProvider to wrapper to ensure a clean instance for every test - Refactor useStacSearch.test.ts to use setupStacSearch helper for all tests, adding a step to wait for IDLE stacApi
- Refactor useStacSearch to use TanStack Query for fetching and caching - Replace manual state management with useQuery for search and pagination - Add FetchRequest type to handle both search POST and pagination GET requests - Improve pagination handling to properly extract and manage next/prev links - Clear pagination links only when API changes, not on every new search - Add queryClient.invalidateQueries on API instance change for cache cleanup - Fix type casting in useItem reload function for consistency - Update tests to wrap pagination link assertions in waitFor to prevent race conditions - Maintain backwards compatibility with existing loading states and API
…er initialization order - Add @tanstack/react-query to vite external dependencies to prevent bundling - Refactor StacApiProvider to split into inner/outer components, ensuring QueryClient is available before any React Query hooks execute - Add @tanstack/react-query peer dependency to example app package.json
- Add insertTypesEntry: true to vite-plugin-dts config to generate index.d.ts - Ensures TypeScript can resolve module declarations when package is linked or installed
- Add centralized query key generators in src/utils/queryKeys.ts - Extract minimal parameters instead of hashing full request objects - Update all hooks to use optimized query key generators Reduces hashing costs for React Query cache lookups by generating smaller, more stable keys with only search-relevant parameters.
- Add enableDevTools prop to StacApiProvider (defaults to false) - Remove automatic DevTools exposure in development mode - Make window.__TANSTACK_QUERY_CLIENT__ optional to prevent type conflicts - Update example app to enable DevTools in development This prevents the library from overwriting QueryClient instances in consuming applications while still allowing opt-in DevTools support.
Clarifies that gcTime (renamed from cacheTime in TanStack Query v5) controls inactive query memory retention and why it's set to 0 in tests.
- Create ApiError class extending Error in src/utils/ApiError.ts - Rename ApiError type to ApiErrorType to avoid naming conflict - Update all hooks to throw new ApiError instances
5a59d65 to
0b4bcdc
Compare
…ient - Remove queryClient prop from StacApiProviderType - Detect parent via QueryClientContext instead of accepting prop - Update docs to show QueryClientProvider wrapping pattern - Document missing options and enableDevTools props in API reference This simplifies the API and prevents duplicate QueryClient instances.
Add tests for StacApiProvider covering QueryClient management, DevTools integration, and context provisioning. - Creates QueryClient when no parent exists - Uses parent QueryClient without nesting providers - DevTools respects enableDevTools prop - Provides stacApi and context methods
BREAKING CHANGE: Remove collections, setCollections, getItem, addItem, deleteItem from context. Context now only provides stacApi. Users should use useCollections() hook directly instead of accessing collections from context. React Query cache is now the single source of truth for data.
… QueryClient" This reverts commit 3ddb6f2.
- Configure a custom QueryClient with specific caching strategies - Pass the queryClient instance to StacApiProvider via prop This change clarifies how to set up and use a custom QueryClient, and investigates the impact of multiple QueryClientProviders on app behavior.
- Update example with staleTime and gcTime, and enableDevTools usage - Document queryClient, options, and enableDevTools props in Component Properties - Fix typo: items.description -> item.description in useItem example - Fix malformed Error type table markdown
BREAKING CHANGE: Hooks no longer return `state` property. Use `isLoading` and `isFetching` booleans instead. - Update hooks to return React Query's isLoading/isFetching directly - Update all tests to check isLoading instead of state - Update example app (Main/index.jsx, ItemDetails.jsx) to use isLoading - Update README with new API documentation Eliminates duplicate state management and provides more granular loading control.
…point
- Add getCollection(collectionId) method to StacApi class
- Refactor useCollection to query /collections/{collectionId} directly
- Add generateCollectionQueryKey for independent collection caching
- Update tests to mock single collection endpoint instead of filtering
Previously, useCollection fetched all collections and filtered to find one.
Now it uses the STAC spec's dedicated endpoint, improving efficiency for
large catalogs and enabling independent collection caching.
- Change reload return type from () => void to () => Promise<QueryObserverResult> - Remove incorrect type casts from refetch in useCollection, useCollections, useItem - Update tests to await reload() calls with proper act() wrapping - Remove eslint-disable comment from useItem test - Remove debounce wrapper from useCollections reload (was returning void) The refetch function from React Query returns a Promise, not void. This fix properly reflects the async nature of reload operations and resolves React act() warnings in tests. Debouncing was removed from useCollections.reload as it's uncommon to debounce explicit reload actions.
BREAKING CHANGE: reload() method renamed to refetch() in useCollection, useCollections, and useItem hooks. - Rename reload() to refetch() in useCollection, useCollections, and useItem - Update all tests to use refetch() instead of reload() - Maintain proper async typing with Promise<QueryObserverResult> Using refetch aligns with React Query's naming convention and makes the API more consistent with the underlying library. The function returns the Promise from React Query's refetch directly. Migration: Replace .reload() with .refetch() in your code.
- Changed dateRangeFrom and dateRangeTo from required to optional in return type - Updated state initialization to use undefined instead of empty strings - Updated setters to accept optional string parameters - Updated reset function to set undefined instead of empty strings This resolves type inconsistencies where the return type claimed required strings but the implementation initialized with empty strings. Conceptually, users should be able to clear/unset date filters by passing undefined.
- Add url field to ApiError class for better debugging context - Add try/catch blocks around all response.json() calls in success paths - Include original error messages in JSON parsing error details - Update ApiErrorType to include optional url field Previously, if a server returned a successful status (200) but invalid JSON (e.g., HTML error pages, malformed responses), the hooks would throw generic parse errors. Now they throw structured ApiError instances with context about what went wrong and which URL failed.
- Add cancel method to debounce utility function with proper TypeScript typing - Add cleanup effect in useStacSearch to cancel pending debounced calls - Prevent orphaned timeouts when component unmounts or _submit function changes Previously, when _submit changed, a new debounced function was created but the old one's timeout wasn't cancelled, causing potential memory leaks and race conditions where stale searches could execute after newer ones.
There was a problem hiding this comment.
Hey Alice, great job on all the documentation! I found the migration doc super helpful and awesome 🙌🏼 . I've reviewed but its definitely quite a large PR with a lot of meaty things so I wasn't able to review as in-depth as i'd like, it would take me a lot longer or several iterations. Hoping other team members can jump in to help review too. cc @dzole0311 @danielfdsilva @ifsimicoded.
Maybe next time it might be a good idea to do this in iterative PRs (adding tanstack query => refactoring one of the hooks => refactoring another => docs => etc) so it could be alot easier to review and merge 🙇🏼♀️
- Add handleStacResponse<T>() utility function to remove duplication - Refactor useCollection, useCollections, useItem, useStacSearch to use utility - Add test suite for handleStacResponse - Add test suite for StacApi class methods
- Add StacHook base interface with common properties (isLoading, isFetching, error) - Add StacRefetchFn<T> type for standardized refetch signatures - Update all hooks to extend StacHook interface (StacCollectionHook, StacCollectionsHook, StacItemHook, StacSearchHook)
|
Thanks everyone for the thorough reviews! 🙏 @sandrahoang686 - All of your suggestions have been implemented, and questions answered. @danielfdsilva - Your centralization suggestion was spot on! I went with the utility function approach (
All tests passing, TypeScript clean, and ready to go! 🚀 I'm merging this to |
Close #25
This PR migrates all data fetching hooks (useCollections, useItem, useStacApi, useStacSearch) to use TanStack Query for caching and state management, replacing the previous manual fetch implementation.
Architecture
deleteItem from context. Context now only provides stacApi. Users should
use useCollections() hook directly instead of accessing collections from
context.
stateproperty. UseisLoadingand
isFetchingbooleans instead.useCollections, and useItem hooks.
Documentation
Migration Notes
This change adds @tanstack/react-query as a peer dependency. Consuming applications must install it!
For details, please note the migration guide as well: docs/MIGRATION.md