adynato-mobile-api
from adynato/skills
AI agent skills for Adynato projects - SEO, web, mobile, and API development conventions
1 stars0 forksUpdated Jan 20, 2026
npx skills add https://github.com/adynato/skills --skill adynato-mobile-apiSKILL.md
Mobile API Skill
Use this skill when integrating APIs into Adynato mobile apps.
Stack
- Data Fetching: TanStack Query (React Query)
- HTTP Client: Fetch API or Axios
- Auth Storage: expo-secure-store
- Offline: TanStack Query persistence
Setup
Query Client Configuration
// lib/query-client.ts
import { QueryClient } from '@tanstack/react-query'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5, // 5 minutes
gcTime: 1000 * 60 * 30, // 30 minutes (formerly cacheTime)
retry: 2,
refetchOnWindowFocus: false, // Mobile doesn't have window focus
},
mutations: {
retry: 1,
},
},
})
Provider Setup
// app/_layout.tsx
import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from '@/lib/query-client'
export default function RootLayout() {
return (
<QueryClientProvider client={queryClient}>
<Stack />
</QueryClientProvider>
)
}
API Client
Base Configuration
// lib/api.ts
import * as SecureStore from 'expo-secure-store'
const API_URL = process.env.EXPO_PUBLIC_API_URL
interface RequestOptions extends RequestInit {
requireAuth?: boolean
}
export async function api<T>(
endpoint: string,
options: RequestOptions = {}
): Promise<T> {
const { requireAuth = true, ...fetchOptions } = options
const headers: HeadersInit = {
'Content-Type': 'application/json',
...fetchOptions.headers,
}
if (requireAuth) {
const token = await SecureStore.getItemAsync('auth_token')
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
}
const response = await fetch(`${API_URL}${endpoint}`, {
...fetchOptions,
headers,
})
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new ApiError(response.status, error.error || 'Request failed')
}
// Handle 204 No Content
if (response.status === 204) {
return undefined as T
}
return response.json()
}
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
this.name = 'ApiError'
}
}
API Functions
// lib/api/users.ts
import { api } from '@/lib/api'
export interface User {
id: string
email: string
name: string
}
export const usersApi = {
getMe: () => api<{ data: User }>('/api/users/me'),
getById: (id: string) => api<{ data: User }>(`/api/users/${id}`),
update: (id: string, data: Partial<User>) =>
api<{ data: User }>(`/api/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(data),
}),
}
Query Hooks
Basic Query
// hooks/useUser.ts
import { useQuery } from '@tanstack/react-query'
import { usersApi } from '@/lib/api/users'
export function useUser(id: string) {
return useQuery({
queryKey: ['users', id],
queryFn: () => usersApi.getById(id),
enabled: !!id,
})
}
Query with Transform
export function useCurrentUser() {
return useQuery({
queryKey: ['users', 'me'],
queryFn: usersApi.getMe,
select: (response) => response.data, // Extract data from wrapper
})
}
Paginated Query
import { useInfiniteQuery } from '@tanstack/react-query'
export function useUsersList() {
return useInfiniteQuery({
queryKey: ['users', 'list'],
queryFn: ({ pageParam = 1 }) =>
api(`/api/users?page=${pageParam}&limit=20`),
getNextPageParam: (lastPage, pages) => {
if (lastPage.data.length < 20) return undefined
return pages.length + 1
},
initialPageParam: 1,
})
}
Mutations
Basic Mutation
// hooks/useUpdateProfile.ts
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { usersApi } from '@/lib/api/users'
export function useUpdateProfile() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: string; data: Partial<User> }) =>
usersApi.update(id, data),
onSuccess: (response, { id }) => {
// Update cache
queryClient.setQueryData(['users', id], response)
queryClient.invalidateQueries({ queryKey: ['users', 'me'] })
},
})
}
Optimistic Update
export function useToggleFavorite() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: (itemId: string) => api(`/api/favorites/${itemId}`, {
method: 'POST'
}),
onMutate: async (itemId) => {
// Cancel outgoing refetches
await queryClient.cancelQueries({ queryKey: ['items', itemId] })
// Snapshot previous value
const previousItem = queryClient.getQueryData(['items', itemId])
// Optimistically update
queryClient.setQueryData(['items', itemId], (old: any) => ({
...old,
isFavorite: !old.isFavorite,
}))
return { previousItem }
},
onError
...
Repository
adynato/skillsParent repository
Repository Stats
Stars1
Forks0