# frontend-data
> Implement data fetching with tRPC queries, mutations, and cache management
- Author: Raphael Mansueto
- Repository: raphaelmans/next16bp
- Version: 20260107004916
- Stars: 0
- Forks: 0
- Last Updated: 2026-02-07
- Source: https://github.com/raphaelmans/next16bp
- Web: https://mule.run/skillshub/@@raphaelmans/next16bp~frontend-data:20260107004916
---
---
name: frontend-data
description: Implement data fetching with tRPC queries, mutations, and cache management
---
# Frontend Data Fetching
Use this skill when implementing data fetching patterns with tRPC and TanStack Query.
## When to Use
- Fetching server data in components
- Creating mutations with cache invalidation
- Implementing loading/error states
- Setting up dependent or parallel queries
- Optimistic updates
## Prerequisites
- tRPC router endpoint exists on the backend
- Component is a Client Component (`'use client'`)
## Steps
### 1. Identify Query Pattern
Determine which pattern fits your use case:
| Pattern | Use Case |
|---------|----------|
| Basic Query | Single data fetch |
| Dependent Query | Data depends on another query's result |
| Parallel Queries | Multiple independent fetches |
| Mutation | Create/update/delete operations |
| Optimistic Update | Instant UI feedback before server confirms |
### 2. Implement Query
#### Basic Query
```typescript
import { trpc } from '@/lib/trpc/client'
function MyComponent() {
const profileQuery = trpc.profile.getByCurrentUser.useQuery({
signedAssets: true,
})
if (profileQuery.isLoading) return
if (profileQuery.isError) return
return
{profileQuery.data.name}
}
```
#### Dependent Queries
```typescript
// First query
const profileQuery = trpc.profile.getByCurrentUser.useQuery()
// Second query - only runs when first has data
const detailsQuery = trpc.profile.getDetails.useQuery(
{ profileId: profileQuery.data?.id ?? '' },
{ enabled: !!profileQuery.data?.id }
)
```
#### Parallel Queries
```typescript
// These run simultaneously
const profileQuery = trpc.profile.getByCurrentUser.useQuery()
const companiesQuery = trpc.company.list.useQuery()
const tagsQuery = trpc.tags.list.useQuery()
```
### 3. Implement Mutation with Cache Invalidation
```typescript
import { trpc } from '@/lib/trpc/client'
function MyForm() {
const trpcUtils = trpc.useUtils()
const updateMut = trpc.profile.update.useMutation()
const onSubmit = async (data: FormData) => {
await updateMut.mutateAsync(data)
// Invalidate affected queries
await Promise.all([
trpcUtils.profile.getByCurrentUser.invalidate(),
trpcUtils.profile.getById.invalidate({ id: data.id }),
])
}
}
```
### 4. Add Loading States
Create a skeleton component matching your UI structure:
```typescript
export function ProfileSkeleton() {
return (
{Array.from({ length: 5 }).map((_, i) => (
))}
)
}
```
### 5. Handle Errors
Use the `useCatchErrorToast` hook for mutations:
```typescript
const catchErrorToast = useCatchErrorToast()
const onSubmit = async (data: FormData) => {
return catchErrorToast(
async () => {
await mutation.mutateAsync(data)
router.push(appRoutes.success)
},
{ description: 'Saved successfully!' }
)
}
```
For queries, use custom retry logic:
```typescript
const query = trpc.resource.getById.useQuery(
{ id },
{
retry: (attempt, error) => {
if (utils.isTRPCNotFoundError(error)) return false
return attempt <= 3
},
}
)
```
### 6. (Optional) Implement Optimistic Updates
For instant UI feedback:
```typescript
const likeMutation = trpc.post.like.useMutation({
onMutate: async (newLike) => {
// Cancel outgoing refetches
await trpcUtils.post.getById.cancel({ id: newLike.postId })
// Snapshot previous value
const previous = trpcUtils.post.getById.getData({ id: newLike.postId })
// Optimistically update
trpcUtils.post.getById.setData({ id: newLike.postId }, (old) => ({
...old!,
likes: old!.likes + 1,
}))
return { previous }
},
onError: (err, newLike, context) => {
// Rollback on error
trpcUtils.post.getById.setData({ id: newLike.postId }, context?.previous)
},
onSettled: () => {
// Refetch to ensure consistency
trpcUtils.post.getById.invalidate()
},
})
```
## Invalidation Strategies
| Method | Use Case |
|--------|----------|
| `trpcUtils.resource.invalidate()` | Invalidate all queries for a procedure |
| `trpcUtils.resource.getById.invalidate({ id })` | Invalidate specific query |
| `Promise.all([...])` | Parallel invalidation |
## Checklist
- [ ] Query uses correct tRPC procedure
- [ ] Loading state shows skeleton
- [ ] Error state handled appropriately
- [ ] Mutations invalidate affected queries
- [ ] `enabled` option used for dependent queries
- [ ] Error toast used for mutation failures
## References
See `references/data-patterns.md` for detailed patterns.