API Integration
The app uses @logbook/sdk with TanStack Query for data fetching.
SDK Setup
The SDK is installed from the Buildkite package registry:
bash
npm install @logbook/sdkConfiguration
typescript
// lib/api.ts
import { createClient } from "@logbook/sdk/client"
import { API_URL } from "@/constants/config"
export const api = createClient({
baseUrl: API_URL,
})Authentication
The SDK automatically includes auth tokens:
typescript
// Tokens are managed by the auth store
import { useAuthStore } from "@/stores/authStore"
const { session } = useAuthStore()
// Pass to SDK
api.setAuthToken(session?.accessToken)TanStack Query
Setup
typescript
// app/_layout.tsx
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
const queryClient = new QueryClient()
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
{/* ... */}
</QueryClientProvider>
)
}Queries
typescript
import { useQuery } from "@tanstack/react-query"
import { api } from "@/lib/api"
function BookList() {
const { data, isLoading, error } = useQuery({
queryKey: ["books"],
queryFn: () => api.books.list(),
})
if (isLoading) return <Loading />
if (error) return <Error message={error.message} />
return (
<FlatList
data={data}
renderItem={({ item }) => <BookCard book={item} />}
/>
)
}Mutations
typescript
import { useMutation, useQueryClient } from "@tanstack/react-query"
function AddBookButton({ book }) {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: () => api.books.create(book),
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries({ queryKey: ["books"] })
},
})
return (
<Button
onPress={() => mutation.mutate()}
disabled={mutation.isPending}
>
{mutation.isPending ? "Adding..." : "Add Book"}
</Button>
)
}Query Keys
Organize query keys consistently:
typescript
const queryKeys = {
books: {
all: ["books"] as const,
detail: (id: string) => ["books", id] as const,
byUser: (userId: string) => ["books", "user", userId] as const,
},
readings: {
all: ["readings"] as const,
current: ["readings", "current"] as const,
},
}Error Handling
typescript
const { error } = useQuery({
queryKey: ["books"],
queryFn: () => api.books.list(),
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
// Handle specific errors
if (error?.status === 401) {
// Redirect to login
}Optimistic Updates
typescript
const mutation = useMutation({
mutationFn: updateBook,
onMutate: async (newBook) => {
await queryClient.cancelQueries({ queryKey: ["books", newBook.id] })
const previousBook = queryClient.getQueryData(["books", newBook.id])
queryClient.setQueryData(["books", newBook.id], newBook)
return { previousBook }
},
onError: (err, newBook, context) => {
queryClient.setQueryData(["books", newBook.id], context.previousBook)
},
})