feat: wire up example components with TanStack Query v6

Phase 5 implementation - Component wiring examples:

1. JobSelectionSidebar.svelte:
   - Replaced createJobResource() with createQuery(() => jobQueries.list())
   - Removed manual loading/error state management
   - Uses TanStack Query's isLoading, isError, refetch() for state
   - Demonstrates client-side query pattern

2. teams/[id]/+page.svelte:
   - Added createQuery with withInitialData() for SSR integration
   - Server-fetched party data used as initial cache value
   - Query can refetch in background when data becomes stale
   - Demonstrates SSR + TanStack Query hybrid pattern

3. Added MIGRATION.md with follow-up prompts for:
   - JobSkillSelectionSidebar migration
   - Search modal migration
   - User profile page migration
   - Teams explore page migration
   - Party component mutations
   - Resource class removal

This completes Phase 5 of the TanStack Query integration.

Co-Authored-By: Justin Edmund <justin@jedmund.com>
This commit is contained in:
Devin AI 2025-11-29 08:16:22 +00:00
parent e1c330f376
commit cce93e367b
3 changed files with 258 additions and 33 deletions

View file

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte' import { createQuery } from '@tanstack/svelte-query'
import type { Job } from '$lib/types/api/entities' import type { Job } from '$lib/types/api/entities'
import { createJobResource } from '$lib/api/adapters/resources/job.resource.svelte' import { jobQueries } from '$lib/api/queries/job.queries'
import { getJobTierName, getJobTierOrder } from '$lib/utils/jobUtils' import { getJobTierName, getJobTierOrder } from '$lib/utils/jobUtils'
import JobItem from '../job/JobItem.svelte' import JobItem from '../job/JobItem.svelte'
import JobTierSelector from '../job/JobTierSelector.svelte' import JobTierSelector from '../job/JobTierSelector.svelte'
@ -17,14 +17,13 @@
let { currentJobId, onSelectJob }: Props = $props() let { currentJobId, onSelectJob }: Props = $props()
// Create job resource // TanStack Query v6: Use createQuery with thunk pattern for reactivity
const jobResource = createJobResource() // Jobs are cached for 30 minutes and shared across all components
const jobsQuery = createQuery(() => jobQueries.list())
// State // State for filtering (local UI state, not server state)
let searchQuery = $state('') let searchQuery = $state('')
let selectedTiers = $state<Set<string>>(new Set(['4', '5', 'ex2', 'o1'])) // Default to IV, V, EXII, OI let selectedTiers = $state<Set<string>>(new Set(['4', '5', 'ex2', 'o1'])) // Default to IV, V, EXII, OI
let loading = $state(false)
let error = $state<string | undefined>()
// Available tiers with short labels for display // Available tiers with short labels for display
const tiers = [ const tiers = [
@ -48,29 +47,11 @@
selectedTiers = newSet selectedTiers = newSet
} }
// Fetch jobs on mount
onMount(() => {
loadJobs()
})
async function loadJobs() {
loading = true
error = undefined
try {
await jobResource.fetchJobs()
} catch (e: any) {
error = e.message || 'Failed to load jobs'
console.error('Error loading jobs:', e)
} finally {
loading = false
}
}
// Filter jobs based on search and filters // Filter jobs based on search and filters
// TanStack Query handles loading/error states, we just filter the data
const filteredJobs = $derived( const filteredJobs = $derived(
(() => { (() => {
let jobs = jobResource.jobs.data || [] let jobs = jobsQuery.data || []
// Filter by search query // Filter by search query
if (searchQuery) { if (searchQuery) {
@ -135,16 +116,16 @@
</div> </div>
<div class="jobs-container"> <div class="jobs-container">
{#if loading} {#if jobsQuery.isLoading}
<div class="loading-state"> <div class="loading-state">
<Icon name="loader-2" size={32} /> <Icon name="loader-2" size={32} />
<p>Loading jobs...</p> <p>Loading jobs...</p>
</div> </div>
{:else if error} {:else if jobsQuery.isError}
<div class="error-state"> <div class="error-state">
<Icon name="alert-circle" size={32} /> <Icon name="alert-circle" size={32} />
<p>{error}</p> <p>{jobsQuery.error?.message || 'Failed to load jobs'}</p>
<Button size="small" onclick={loadJobs}>Retry</Button> <Button size="small" onclick={() => jobsQuery.refetch()}>Retry</Button>
</div> </div>
{:else if Object.keys(filteredJobs).length === 0} {:else if Object.keys(filteredJobs).length === 0}
<div class="empty-state"> <div class="empty-state">

217
src/lib/query/MIGRATION.md Normal file
View file

@ -0,0 +1,217 @@
# TanStack Query Migration Guide
This document contains follow-up prompts for migrating remaining components to TanStack Query v6.
## Migration Status
### Completed (PR #441)
- Query options factories: `party.queries.ts`, `job.queries.ts`, `user.queries.ts`
- Mutation configurations: `party.mutations.ts`, `grid.mutations.ts`, `job.mutations.ts`
- SSR utilities: `withInitialData`, `prefetchQuery`, `prefetchInfiniteQuery`
- Example components: `JobSelectionSidebar.svelte`, `teams/[id]/+page.svelte`
### Pending Migration
The following components still use direct adapter calls or resource classes and should be migrated in future PRs.
---
## Follow-Up Prompt 1: Job Skill Selection Sidebar
**Scope**: Migrate `JobSkillSelectionSidebar.svelte` to use TanStack Query
**Prompt**:
```
Migrate the JobSkillSelectionSidebar component to use TanStack Query v6.
The component currently uses InfiniteScrollResource for paginated skill loading.
Replace it with createInfiniteQuery using jobQueries.skills().
Files to modify:
- src/lib/components/sidebar/JobSkillSelectionSidebar.svelte
Reference implementation:
- src/lib/components/sidebar/JobSelectionSidebar.svelte (already migrated)
- src/lib/api/queries/job.queries.ts (jobQueries.skills for infinite query)
Key changes:
1. Replace InfiniteScrollResource with createInfiniteQuery
2. Use jobQueries.skills(jobId, { query: searchTerm }) for the query options
3. Handle pagination with query.fetchNextPage() and query.hasNextPage
4. Update loading/error states to use query.isLoading, query.isError
```
---
## Follow-Up Prompt 2: Search Modal Migration
**Scope**: Migrate search functionality to use TanStack Query
**Prompt**:
```
Migrate the search functionality to use TanStack Query v6.
The existing search.queries.ts has infinite query options for weapons, characters,
summons, and job skills. Wire these up to the actual search components.
Files to modify:
- src/lib/components/search/SearchPanel.svelte (or equivalent)
- src/lib/features/search/openSearchSidebar.svelte.ts
Reference:
- src/lib/api/queries/search.queries.ts (existing query options)
- src/lib/components/InfiniteScrollQuery.svelte (existing TanStack Query component)
Key changes:
1. Use createInfiniteQuery with searchQueries.weapons/characters/summons
2. Implement debounced search input (debounce the value, not the query)
3. Use InfiniteScrollQuery component for rendering results
4. Remove dependency on SearchResource class
```
---
## Follow-Up Prompt 3: User Profile Page
**Scope**: Migrate user profile page to use TanStack Query with SSR
**Prompt**:
```
Migrate the [username]/+page.svelte to use TanStack Query v6 with SSR.
The page currently fetches user profile and parties in +page.server.ts.
Add TanStack Query integration using the withInitialData pattern.
Files to modify:
- src/routes/[username]/+page.svelte
Reference:
- src/routes/teams/[id]/+page.svelte (already migrated with withInitialData)
- src/lib/api/queries/user.queries.ts (userQueries.profile, userQueries.parties)
- src/lib/query/ssr.ts (withInitialData helper)
Key changes:
1. Add createQuery for user profile with withInitialData
2. Add createInfiniteQuery for user parties with initialData from server
3. Use $derived to prefer query data over server data
4. Enable background refetching for fresh data
```
---
## Follow-Up Prompt 4: Teams Explore Page
**Scope**: Migrate teams explore page to use TanStack Query
**Prompt**:
```
Migrate the teams/explore page to use TanStack Query v6 for party listing.
The page displays a paginated list of public parties with filtering.
Files to modify:
- src/routes/teams/explore/+page.svelte
- src/routes/teams/explore/+page.server.ts (if needed)
Reference:
- src/lib/api/queries/party.queries.ts (partyQueries.list for infinite query)
- src/lib/query/ssr.ts (prefetchInfiniteQuery for SSR)
Key changes:
1. Use createInfiniteQuery with partyQueries.list(filters)
2. Implement filter state that triggers query refetch
3. Use infinite scroll or "Load More" button with query.fetchNextPage()
4. Consider prefetching first page in +page.ts for faster initial load
```
---
## Follow-Up Prompt 5: Party Component Mutations
**Scope**: Wire up Party component to use TanStack Query mutations
**Prompt**:
```
Migrate the Party component to use TanStack Query mutations for CRUD operations.
The Party component currently uses PartyService and GridService directly.
Replace these with TanStack Query mutations for automatic cache invalidation.
Files to modify:
- src/lib/components/party/Party.svelte
Reference:
- src/lib/api/mutations/party.mutations.ts (useUpdateParty, useDeleteParty, etc.)
- src/lib/api/mutations/grid.mutations.ts (useUpdateGridWeapon, etc.)
- src/lib/api/mutations/job.mutations.ts (useUpdateJob, useUpdateSkills)
Key changes:
1. Import and use mutation hooks (useUpdateParty, useDeleteParty, etc.)
2. Replace direct service calls with mutation.mutate()
3. Leverage optimistic updates for immediate UI feedback
4. Use mutation.isPending for loading states
5. Use mutation.error for error handling
Note: This is a larger refactor. Consider breaking it into sub-tasks:
- 5a: Party metadata mutations (name, description, visibility)
- 5b: Grid weapon mutations
- 5c: Grid character mutations
- 5d: Grid summon mutations
- 5e: Job and skill mutations
```
---
## Follow-Up Prompt 6: Remove Deprecated Resource Classes
**Scope**: Remove deprecated resource classes after migration is complete
**Prompt**:
```
Remove the deprecated resource classes now that TanStack Query migration is complete.
Files to delete:
- src/lib/api/adapters/resources/search.resource.svelte.ts
- src/lib/api/adapters/resources/party.resource.svelte.ts
- src/lib/api/adapters/resources/job.resource.svelte.ts
- src/lib/api/adapters/resources/infiniteScroll.resource.svelte.ts
Pre-requisites:
1. Verify no components import these files (use grep)
2. Ensure all functionality has been migrated to TanStack Query
3. Run build to confirm no import errors
Steps:
1. Search for any remaining imports of resource classes
2. Migrate any remaining usages to TanStack Query
3. Delete the resource files
4. Update any barrel exports (index.ts files)
5. Run build and tests to verify
```
---
## Migration Checklist
Use this checklist to track overall migration progress:
- [x] Create query options factories
- [x] Create mutation configurations
- [x] Add SSR utilities
- [x] Migrate JobSelectionSidebar (example)
- [x] Migrate teams/[id] page (SSR example)
- [ ] Migrate JobSkillSelectionSidebar
- [ ] Migrate search functionality
- [ ] Migrate user profile page
- [ ] Migrate teams explore page
- [ ] Migrate Party component mutations
- [ ] Remove deprecated resource classes
- [ ] Add TanStack Query devtools (optional)
## Notes
- Always test locally before pushing changes
- Run `npm run build` to verify TypeScript compilation
- The existing adapters remain unchanged - TanStack Query wraps them
- Cache invalidation is handled automatically by mutations
- SSR uses hybrid approach: existing +page.server.ts + withInitialData pattern

View file

@ -1,12 +1,39 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types' import type { PageData } from './$types'
import Party from '$lib/components/party/Party.svelte' import Party from '$lib/components/party/Party.svelte'
import { createQuery } from '@tanstack/svelte-query'
import { partyQueries } from '$lib/api/queries/party.queries'
import { withInitialData } from '$lib/query/ssr'
let { data }: { data: PageData } = $props() let { data }: { data: PageData } = $props()
/**
* TanStack Query v6 SSR Integration Example
*
* This demonstrates the `withInitialData` pattern for pages using +page.server.ts.
* The server-fetched party data is used as initial data for the query, which means:
*
* 1. No loading state on initial render (data is already available)
* 2. The query can refetch in the background when data becomes stale
* 3. The data is cached and shared across components using the same query key
*
* Note: The Party component currently manages its own state, so we pass the
* server data directly. In a future refactor, the Party component could use
* this query directly for automatic cache updates and background refetching.
*/
const partyQuery = createQuery(() => ({
...partyQueries.byShortcode(data.party?.shortcode ?? ''),
...withInitialData(data.party),
enabled: !!data.party?.shortcode
}))
// Use query data if available, fall back to server data
// This allows the query to refetch and update the UI automatically
const party = $derived(partyQuery.data ?? data.party)
</script> </script>
{#if data?.party} {#if party}
<Party party={data.party} canEdit={data.canEdit || false} authUserId={data.authUserId} /> <Party party={party} canEdit={data.canEdit || false} authUserId={data.authUserId} />
{:else} {:else}
<div> <div>
<h1>Party not found</h1> <h1>Party not found</h1>