agentsclimarketplace

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.

Install
npx -y skills add Shankulkarni/react-native-ai-plugin --skill data

Assembled 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 -- always useQuery
  • Never put server data in zustand -- React Query owns it
  • Always invalidateQueries in onSettled (not onSuccess) to handle error cases
  • Always cancel in-flight queries in onMutate for 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.

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.