Data
Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/data
Write better React Native code from day one - opinionated Claude Code rules, New Architecture ready.
npx -y skills add Shankulkarni/react-native-ai-plugin --skill dataAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when fetching or mutating server data -- React Query setup, axios instance, query key factories, useQuery/useMutation hooks, error handling, pagination, optimistic updates.
SKILL.md
5.8 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it
Data Fetching -- React Query + axios
Backend-agnostic. React Query owns all server data. Never fetch in useEffect.
QueryClient (src/libs/query-client.ts)
import { MutationCache, QueryCache, QueryClient } from '@tanstack/react-query'
import { toast } from 'sonner-native'
export const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: Infinity,
gcTime: Infinity,
retry: false,
refetchOnMount: false,
refetchOnWindowFocus: false,
},
},
queryCache: new QueryCache({
onError: (error) => toast.error(error.message),
}),
mutationCache: new MutationCache({
onError: (error) => toast.error(error.message),
}),
})
axios Instance (src/libs/axios.ts)
import axios from 'axios'
import * as SecureStore from 'expo-secure-store'
export const api = axios.create({
baseURL: process.env.EXPO_PUBLIC_API_URL,
timeout: 30_000,
headers: { 'Content-Type': 'application/json' },
})
// Attach auth token
api.interceptors.request.use(async (config) => {
const token = await SecureStore.getItemAsync('access_token')
if (token) config.headers.Authorization = `Bearer ${token}`
return config
})
// Refresh token on 401
api.interceptors.response.use(
(res) => res,
async (error) => {
if (error.response?.status === 401) {
// refresh logic here
}
return Promise.reject(error)
},
)
API Module Structure
4 files per domain in src/api/<domain>/:
src/api/posts/
├── types.ts ← Zod schemas + inferred types
├── api.ts ← axios calls (pure functions)
├── keys.ts ← query key factory
└── hooks.ts ← useQuery/useMutation wrappers
types.ts
import { z } from 'zod'
export const postSchema = z.object({
id: z.string(),
title: z.string(),
body: z.string(),
createdAt: z.string(),
})
export type Post = z.infer<typeof postSchema>
export const createPostSchema = z.object({
title: z.string().min(1),
body: z.string().min(1),
})
export type CreatePostInput = z.infer<typeof createPostSchema>
api.ts
import { api } from '@/libs/axios'
import type { Post, CreatePostInput } from './types'
export async function fetchPosts(): Promise<Post[]> {
const { data } = await api.get('/posts')
return data
}
export async function fetchPost(id: string): Promise<Post> {
const { data } = await api.get(`/posts/${id}`)
return data
}
export async function createPost(input: CreatePostInput): Promise<Post> {
const { data } = await api.post('/posts', input)
return data
}
export async function deletePost(id: string): Promise<void> {
await api.delete(`/posts/${id}`)
}
keys.ts
export const postKeys = {
all: ['posts'] as const,
lists: () => [...postKeys.all, 'list'] as const,
list: (params?: Record<string, unknown>) => [...postKeys.lists(), params] as const,
details: () => [...postKeys.all, 'detail'] as const,
detail: (id: string) => [...postKeys.details(), id] as const,
}
hooks.ts
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { createPost, deletePost, fetchPost, fetchPosts } from './api'
import { postKeys } from './keys'
export function usePosts() {
return useQuery({ queryKey: postKeys.list(), queryFn: fetchPosts })
}
export function usePost(id: string) {
return useQuery({ queryKey: postKeys.detail(id), queryFn: () => fetchPost(id) })
}
export function useCreatePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: createPost,
onSuccess: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}
export function useDeletePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deletePost,
onSuccess: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}
Using in Components
function PostsScreen() {
const { data: posts, isPending, isError } = usePosts()
const { mutate: createPost, isPending: isCreating } = useCreatePost()
if (isPending) return <ActivityIndicator />
if (isError) return <Text>Something went wrong</Text>
return (
<FlashList
data={posts}
renderItem={({ item }) => <PostCard id={item.id} title={item.title} />}
estimatedItemSize={80}
/>
)
}
Pagination (Cursor-based)
// hooks.ts
export function useInfinitePosts() {
return useInfiniteQuery({
queryKey: postKeys.list({ infinite: true }),
queryFn: ({ pageParam }) => fetchPostsPage({ cursor: pageParam }),
initialPageParam: undefined as string | undefined,
getNextPageParam: (lastPage) => lastPage.nextCursor,
})
}
Optimistic Updates
export function useDeletePost() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: deletePost,
onMutate: async (id) => {
await queryClient.cancelQueries({ queryKey: postKeys.lists() })
const previous = queryClient.getQueryData(postKeys.list())
queryClient.setQueryData(postKeys.list(), (old: Post[]) =>
old.filter((p) => p.id !== id),
)
return { previous }
},
onError: (_, __, ctx) => {
queryClient.setQueryData(postKeys.list(), ctx?.previous)
},
onSettled: () => queryClient.invalidateQueries({ queryKey: postKeys.lists() }),
})
}
Rules
- Never fetch in
useEffect-- alwaysuseQuery - Never put server data in zustand -- React Query owns it
- Always
invalidateQueriesinonSettled(notonSuccess) to handle error cases - Always cancel in-flight queries in
onMutatefor optimistic updates staleTime: Infinity-- data never goes stale automatically, refetch manually
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.