react-api

React API Patterns Core Knowledge

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "react-api" with this command: npx skills add claude-dev-suite/claude-dev-suite/claude-dev-suite-claude-dev-suite-react-api

React API Patterns Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: react for comprehensive documentation.

When NOT to Use This Skill

Skip this skill when:

  • Using TanStack Query exclusively (use state-tanstack-query )

  • Working with GraphQL (use Apollo Client or urql)

  • Building non-React applications

  • Server Components handle data fetching (Next.js App Router)

  • Need advanced caching beyond SWR (use state-tanstack-query )

Custom useFetch Hook

import { useState, useEffect, useCallback } from 'react';

interface UseFetchState<T> { data: T | null; loading: boolean; error: Error | null; }

interface UseFetchOptions { immediate?: boolean; }

export function useFetch<T>( url: string, options?: RequestInit & UseFetchOptions ) { const [state, setState] = useState<UseFetchState<T>>({ data: null, loading: options?.immediate !== false, error: null, });

const execute = useCallback(async () => { setState(prev => ({ ...prev, loading: true, error: null }));

try {
  const response = await fetch(url, options);
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}`);
  }
  const data = await response.json();
  setState({ data, loading: false, error: null });
  return data;
} catch (error) {
  setState(prev => ({
    ...prev,
    loading: false,
    error: error as Error,
  }));
  throw error;
}

}, [url, options]);

useEffect(() => { if (options?.immediate !== false) { execute(); } }, [execute, options?.immediate]);

return { ...state, refetch: execute }; }

// Usage function UserProfile({ id }: { id: string }) { const { data: user, loading, error, refetch } = useFetch<User>( /api/users/${id} );

if (loading) return <Spinner />; if (error) return <Error message={error.message} onRetry={refetch} />; return <div>{user?.name}</div>; }

SWR (Stale-While-Revalidate)

npm install swr

Basic Usage

import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

function UserProfile({ id }: { id: string }) { const { data, error, isLoading, mutate } = useSWR<User>( /api/users/${id}, fetcher );

if (isLoading) return <Spinner />; if (error) return <Error />; return <div>{data?.name}</div>; }

Global Configuration

import { SWRConfig } from 'swr';

const fetcher = async (url: string) => { const res = await fetch(url, { headers: { Authorization: Bearer ${getToken()} }, }); if (!res.ok) throw new Error('API Error'); return res.json(); };

function App() { return ( <SWRConfig value={{ fetcher, revalidateOnFocus: true, revalidateOnReconnect: true, dedupingInterval: 2000, errorRetryCount: 3, onError: (error) => console.error(error), }} > <MyApp /> </SWRConfig> ); }

Mutations with useSWRMutation

import useSWRMutation from 'swr/mutation';

async function createUser(url: string, { arg }: { arg: CreateUserDto }) { const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(arg), }); return res.json(); }

function CreateUserForm() { const { trigger, isMutating } = useSWRMutation('/api/users', createUser);

const handleSubmit = async (data: CreateUserDto) => { try { await trigger(data); // Success } catch (error) { // Error } };

return <form onSubmit={handleSubmit}>...</form>; }

Optimistic Updates

function UserList() { const { data, mutate } = useSWR<User[]>('/api/users', fetcher);

const deleteUser = async (id: string) => { // Optimistic update const optimisticData = data?.filter(u => u.id !== id); mutate(optimisticData, { revalidate: false });

try {
  await fetch(`/api/users/${id}`, { method: 'DELETE' });
  mutate(); // Revalidate after success
} catch {
  mutate(data); // Rollback on error
}

};

return ( <ul> {data?.map(user => ( <li key={user.id}> {user.name} <button onClick={() => deleteUser(user.id)}>Delete</button> </li> ))} </ul> ); }

React Suspense with use()

// React 18+ with use() hook import { use, Suspense } from 'react';

// Create promise outside component const userPromise = fetch('/api/users/1').then(res => res.json());

function UserProfile() { const user = use(userPromise); return <div>{user.name}</div>; }

function App() { return ( <Suspense fallback={<Spinner />}> <UserProfile /> </Suspense> ); }

Suspense with SWR

import useSWR from 'swr';

function UserProfile({ id }: { id: string }) { const { data } = useSWR<User>(/api/users/${id}, fetcher, { suspense: true, });

// data is guaranteed to exist (no loading state) return <div>{data.name}</div>; }

function App() { return ( <Suspense fallback={<Spinner />}> <UserProfile id="1" /> </Suspense> ); }

Error Boundaries

import { Component, ReactNode } from 'react';

interface Props { children: ReactNode; fallback?: ReactNode; onError?: (error: Error) => void; }

interface State { hasError: boolean; error: Error | null; }

export class ApiErrorBoundary extends Component<Props, State> { state: State = { hasError: false, error: null };

static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; }

componentDidCatch(error: Error) { this.props.onError?.(error); }

reset = () => { this.setState({ hasError: false, error: null }); };

render() { if (this.state.hasError) { return this.props.fallback ?? ( <div> <h2>Something went wrong</h2> <button onClick={this.reset}>Try again</button> </div> ); } return this.props.children; } }

// Usage function App() { return ( <ApiErrorBoundary fallback={<ErrorFallback />} onError={(error) => logError(error)} > <Suspense fallback={<Spinner />}> <UserProfile /> </Suspense> </ApiErrorBoundary> ); }

Real-Time Updates

Server-Sent Events (SSE)

function useSSE<T>(url: string) { const [data, setData] = useState<T | null>(null); const [error, setError] = useState<Error | null>(null);

useEffect(() => { const eventSource = new EventSource(url);

eventSource.onmessage = (event) => {
  setData(JSON.parse(event.data));
};

eventSource.onerror = () => {
  setError(new Error('SSE connection failed'));
  eventSource.close();
};

return () => eventSource.close();

}, [url]);

return { data, error }; }

// Usage function Notifications() { const { data: notification } = useSSE<Notification>('/api/notifications'); return notification ? <Toast message={notification.message} /> : null; }

WebSocket Hook

function useWebSocket<T>(url: string) { const [data, setData] = useState<T | null>(null); const [isConnected, setIsConnected] = useState(false); const wsRef = useRef<WebSocket | null>(null);

useEffect(() => { const ws = new WebSocket(url); wsRef.current = ws;

ws.onopen = () => setIsConnected(true);
ws.onclose = () => setIsConnected(false);
ws.onmessage = (event) => setData(JSON.parse(event.data));

return () => ws.close();

}, [url]);

const send = useCallback((message: unknown) => { wsRef.current?.send(JSON.stringify(message)); }, []);

return { data, isConnected, send }; }

// Usage function Chat() { const { data: message, send, isConnected } = useWebSocket<Message>( 'wss://api.example.com/chat' );

const handleSend = (text: string) => { send({ type: 'message', text }); };

return ( <div> {isConnected ? 'Connected' : 'Connecting...'} {message && <Message data={message} />} </div> ); }

Loading States

Skeleton Loading

function UserCardSkeleton() { return ( <div className="animate-pulse"> <div className="h-12 w-12 bg-gray-200 rounded-full" /> <div className="h-4 w-32 bg-gray-200 mt-2 rounded" /> <div className="h-3 w-24 bg-gray-200 mt-1 rounded" /> </div> ); }

function UserCard({ id }: { id: string }) { const { data, isLoading } = useSWR<User>(/api/users/${id}, fetcher);

if (isLoading) return <UserCardSkeleton />; return ( <div> <img src={data?.avatar} /> <h3>{data?.name}</h3> <p>{data?.email}</p> </div> ); }

Anti-Patterns

Anti-Pattern Why It's Bad Correct Approach

Fetching in useEffect without cleanup Race conditions Use AbortController

Not handling loading/error states Poor UX Always show loading/error UI

Fetching in render Infinite loops Use useEffect or Suspense

No request deduplication Duplicate requests Use SWR or TanStack Query

Hardcoded API URLs Hard to maintain Use environment variables

Not retrying failed requests Poor resilience Add retry logic

Quick Troubleshooting

Issue Likely Cause Solution

Infinite fetch loop Fetching in render Move to useEffect

Stale data shown No revalidation Configure SWR revalidation

Race condition Multiple rapid requests Use AbortController

Memory leak warning Updating unmounted component Check mounted state or cleanup

Duplicate requests No deduplication Use SWR or caching layer

CORS error Backend not configured Configure CORS headers

Production Readiness

Checklist

  • Global fetcher with auth headers

  • Error boundary for API errors

  • Loading skeletons for better UX

  • Optimistic updates for mutations

  • Request deduplication

  • Automatic retry on failure

  • Revalidation strategy defined

  • Real-time updates where needed

  • Proper cleanup on unmount

Reference Documentation

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: react for comprehensive documentation.

  • Custom Hooks

  • Suspense

  • Error Boundaries

  • SWR

  • Real-Time

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

cron-scheduling

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

token-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

webrtc

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

react-19

No summary provided by upstream source.

Repository SourceNeeds Review