API & Networking
Twinbloc provides a robust networking layer built on Axios, TanStack Query, and React Query Kit. It handles authentication, error parsing, and offline persistence out of the box.
The Client Wrapper
We use a custom wrapper executeRest around Axios to standardize request handling. This function automatically:
- Injects the
Authorizationheader with the current access token. - Handles 401 Unauthorized errors by signing the user out.
- Unwraps the response data.
- Provides typed error handling via
ApiError.
1import { executeRest } from '@/api/common';23// Basic GET request4const data = await executeRest<User>('/users/me', 'GET');56// POST request with body7await executeRest('/auth/login', 'POST', {8 email: 'user@example.com',9 password: 'password'10});1112// Request with custom options (headers, params)13await executeRest('/search', 'GET', undefined, {14 params: { q: 'react' },15 ignore401: true // Don't sign out on 40116});
Reusable Query Hooks
We use react-query-kit to create reusable query hooks. This keeps your API logic co-located and type-safe.
Here is an example of the useGetUser hook located in src/api/user/use-get-user.ts:
1import { createQuery } from "react-query-kit";2import { executeRest, QueryKey } from "../common";3import { type TUser } from "./types";45// 1. Create the base query6const _useGetUser = createQuery<TUser, void, AxiosError>({7 queryKey: [QueryKey.USER], // unique key for caching8 fetcher: async () => {9 const response = await executeRest<{ data: TUser }>("auth/me", "GET");10 return response?.data;11 },12});1314// 2. Export a custom hook wrapper for better DX15export const useGetUser = (userId?: string) => {16 const { auth_data } = useAuth();1718 // Only run query if user is logged in19 return _useGetUser({20 enabled: !!auth_data?.access,21 });22};
Pagination & Infinite Lists
For infinite scrolling lists (like feeds), we provide utilities in api-utils.ts to normalize pages and handle cursors.
1import { createInfiniteQuery } from "react-query-kit";2import { getNextPageParam, normalizePages } from "@/api/common";34const usePosts = createInfiniteQuery<Post[]>({5 queryKey: ['posts'],6 fetcher: async ({ pageParam }) => {7 return executeRest('/posts', 'GET', undefined, {8 params: { cursor: pageParam }9 });10 },11 getNextPageParam, // Automatically extracts 'next', 'cursor', etc.12});1314// Usage in component15const { data } = usePosts();16const allPosts = normalizePages(data?.pages); // Flattens pages into single array
Persistence & Provider
The APIProvider in src/api/common/api-provider.tsx wraps the app with the QueryClient and configures persistence using MMKV.
1import { APIProvider } from '@/api/common';23export default function App() {4 return (5 <APIProvider>6 <RootNavigator />7 </APIProvider>8 );9}
Note: Queries are persisted to MMKV storage by default. You can control this per-query using gcTime and staleTime.
Last updated on 2/10/2026
Edit this page on GitHub