Queries
Queries are used to fetch data from your API. Builder's React Query integration provides type-safe hooks with automatic query key generation and cache management.
Basic Query
Create a query with inline configuration:
const getUser = client.query({
method: 'GET',
url: '/users/$userId',
responseSchema: userSchema,
processResponse: (data) => data,
})
// Use in component
function UserProfile({ userId }: { userId: string }) {
const { data, isLoading, error } = getUser.use({
urlParams: { userId },
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>{data.name}</div>
}
Query from Endpoint
Create a query from a pre-declared endpoint:
// shared/endpoints/users.ts
import { builder } from '@navios/builder'
const API = builder()
export const getUserEndpoint = API.declareEndpoint({
method: 'GET',
url: '/users/$userId',
responseSchema: userSchema,
})
// client/queries/users.ts
import { client } from '../index'
import { getUserEndpoint } from '../../shared/endpoints/users'
const getUser = client.queryFromEndpoint(getUserEndpoint, {
processResponse: (data) => data,
})
// Usage is the same
const { data } = getUser.use({ urlParams: { userId: '123' } })
use() vs useSuspense()
use() Hook
Returns a query result object with loading and error states:
const { data, isLoading, error, refetch } = getUser.use({
urlParams: { userId: '123' },
})
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return <div>{data.name}</div>
useSuspense() Hook
Returns data directly and throws on error (requires Suspense boundary):
function UserProfile({ userId }: { userId: string }) {
const user = getUser.useSuspense({ urlParams: { userId } })
// No loading/error checks needed - Suspense handles it
return <div>{user.name}</div>
}
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<ErrorBoundary fallback={<div>Error!</div>}>
<UserProfile userId="123" />
</ErrorBoundary>
</Suspense>
)
}
Query Options
Enabled
Conditionally enable/disable queries:
const { data } = getUser.use({
urlParams: { userId },
enabled: !!userId, // Only fetch if userId exists
})
Stale Time
Control how long data is considered fresh:
const { data } = getUser.use({
urlParams: { userId },
staleTime: 5 * 60 * 1000, // 5 minutes
})
Cache Time
Control how long unused data stays in cache:
const { data } = getUser.use({
urlParams: { userId },
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
})
Refetch Options
const { data } = getUser.use({
urlParams: { userId },
refetchOnWindowFocus: false,
refetchOnMount: true,
refetchOnReconnect: true,
})
Query Parameters
URL Parameters
const getUser = client.query({
method: 'GET',
url: '/users/$userId',
responseSchema: userSchema,
processResponse: (data) => data,
})
const { data } = getUser.use({
urlParams: { userId: '123' },
})
Query String Parameters
const getUsers = client.query({
method: 'GET',
url: '/users',
querySchema: z.object({
page: z.number().optional(),
limit: z.number().optional(),
}),
responseSchema: z.array(userSchema),
processResponse: (data) => data,
})
const { data } = getUsers.use({
params: { page: 1, limit: 20 },
})
Combined Parameters
const getUserPosts = client.query({
method: 'GET',
url: '/users/$userId/posts',
querySchema: z.object({
page: z.number().optional(),
}),
responseSchema: z.array(postSchema),
processResponse: (data) => data,
})
const { data } = getUserPosts.use({
urlParams: { userId: '123' },
params: { page: 1 },
})
POST Queries
You can use POST for queries (useful for complex search):
const searchUsers = client.query({
method: 'POST',
url: '/users/search',
requestSchema: z.object({
query: z.string(),
filters: z.array(z.string()).optional(),
}),
responseSchema: z.array(userSchema),
processResponse: (data) => data,
})
const { data } = searchUsers.use({
data: {
query: 'john',
filters: ['active'],
},
})
processResponse
Transform response data:
const getUser = client.query({
method: 'GET',
url: '/users/$userId',
responseSchema: userSchema,
processResponse: (data) => ({
...data,
displayName: `${data.firstName} ${data.lastName}`,
isActive: data.status === 'active',
}),
})
// data includes displayName and isActive
const { data } = getUser.use({ urlParams: { userId: '123' } })
Error Handling
With use()
const { data, error, isError } = getUser.use({
urlParams: { userId: '123' },
})
if (isError) {
if (error instanceof NaviosError) {
console.error('API Error:', error.message)
} else {
console.error('Unknown error:', error)
}
}
With useSuspense()
Use ErrorBoundary:
class QueryErrorBoundary extends React.Component {
state = { hasError: false }
static getDerivedStateFromError(error: Error) {
return { hasError: true, error }
}
render() {
if (this.state.hasError) {
return <div>Error: {this.state.error.message}</div>
}
return this.props.children
}
}
function App() {
return (
<QueryErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<UserProfile userId="123" />
</Suspense>
</QueryErrorBoundary>
)
}
Query States
Loading States
const {
data,
isLoading, // Initial load
isFetching, // Any fetch (including background)
isRefetching, // Refetching
isPending, // Alias for isLoading (v5)
} = getUser.use({ urlParams: { userId: '123' } })
Error States
const {
error,
isError,
failureCount,
failureReason,
} = getUser.use({ urlParams: { userId: '123' } })
Success States
const {
data,
isSuccess,
dataUpdatedAt,
status,
} = getUser.use({ urlParams: { userId: '123' } })
Refetching
Manual Refetch
const { data, refetch } = getUser.use({
urlParams: { userId: '123' },
})
// Refetch manually
<button onClick={() => refetch()}>Refresh</button>
Refetch on Interval
const { data } = getUser.use({
urlParams: { userId: '123' },
refetchInterval: 5000, // Refetch every 5 seconds
})
Refetch on Window Focus
const { data } = getUser.use({
urlParams: { userId: '123' },
refetchOnWindowFocus: true,
})
Common Patterns
Conditional Queries
function UserProfile({ userId }: { userId?: string }) {
const { data } = getUser.use({
urlParams: { userId: userId! },
enabled: !!userId, // Only fetch if userId exists
})
if (!userId) return <div>No user selected</div>
return <div>{data.name}</div>
}
Dependent Queries
function UserPosts({ userId }: { userId: string }) {
const { data: user } = getUser.use({ urlParams: { userId } })
const { data: posts } = getPosts.use({
urlParams: { userId },
enabled: !!user, // Only fetch posts after user is loaded
})
return <div>{/* ... */}</div>
}
Parallel Queries
function Dashboard({ userId }: { userId: string }) {
const { data: user } = getUser.use({ urlParams: { userId } })
const { data: posts } = getPosts.use({ urlParams: { userId } })
const { data: comments } = getComments.use({ urlParams: { userId } })
// All queries run in parallel
return <div>{/* ... */}</div>
}
Next Steps
- Infinite Queries - Paginated data
- Mutations - Data modifications
- Query Keys - Query key management
- Invalidation - Cache invalidation