Skip to content

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/sdk

Configuration

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)
	},
})

Built with VitePress