frontend-patterns

Frontend development and API integration patterns for React, TypeScript, and state management

16 stars

Best use case

frontend-patterns is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Frontend development and API integration patterns for React, TypeScript, and state management

Teams using frontend-patterns should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/frontend-patterns/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/development/frontend-patterns/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/frontend-patterns/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How frontend-patterns Compares

Feature / Agentfrontend-patternsStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Frontend development and API integration patterns for React, TypeScript, and state management

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# Frontend Patterns Skill

## Purpose
Build robust frontend applications with proper API integration and state management.

## Data Fetching Patterns

### TanStack Query (React Query)

```typescript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// Query configuration
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000,       // 5 minutes
      gcTime: 30 * 60 * 1000,         // 30 minutes (formerly cacheTime)
      retry: 3,
      retryDelay: (attempt) => Math.min(1000 * 2 ** attempt, 30000),
      refetchOnWindowFocus: false,
    },
  },
});

// Type-safe API client
const api = {
  users: {
    list: async (params: { page: number; limit: number }) => {
      const res = await fetch(`/api/users?${new URLSearchParams(params as any)}`);
      if (!res.ok) throw new ApiError(res);
      return res.json() as Promise<PaginatedResponse<User>>;
    },
    get: async (id: string) => {
      const res = await fetch(`/api/users/${id}`);
      if (!res.ok) throw new ApiError(res);
      return res.json() as Promise<User>;
    },
    create: async (data: CreateUserInput) => {
      const res = await fetch('/api/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
      if (!res.ok) throw new ApiError(res);
      return res.json() as Promise<User>;
    },
  },
};

// Query hook with pagination
function useUsers(page: number) {
  return useQuery({
    queryKey: ['users', 'list', { page }],
    queryFn: () => api.users.list({ page, limit: 20 }),
    placeholderData: (prev) => prev,  // Keep previous data while loading
  });
}

// Single user query
function useUser(id: string) {
  return useQuery({
    queryKey: ['users', 'detail', id],
    queryFn: () => api.users.get(id),
    enabled: !!id,
  });
}

// Mutation with optimistic update
function useCreateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: api.users.create,
    onMutate: async (newUser) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: ['users', 'list'] });

      // Snapshot previous value
      const previous = queryClient.getQueryData(['users', 'list']);

      // Optimistically update
      queryClient.setQueryData(['users', 'list'], (old: any) => ({
        ...old,
        data: [...(old?.data || []), { ...newUser, id: 'temp-id' }],
      }));

      return { previous };
    },
    onError: (err, newUser, context) => {
      // Rollback on error
      queryClient.setQueryData(['users', 'list'], context?.previous);
    },
    onSettled: () => {
      // Refetch after mutation
      queryClient.invalidateQueries({ queryKey: ['users', 'list'] });
    },
  });
}
```

### SWR Pattern

```typescript
import useSWR, { mutate } from 'swr';
import useSWRMutation from 'swr/mutation';

const fetcher = async (url: string) => {
  const res = await fetch(url);
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
};

function useUsers() {
  const { data, error, isLoading, isValidating } = useSWR<User[]>(
    '/api/users',
    fetcher,
    {
      revalidateOnFocus: false,
      dedupingInterval: 5000,
    }
  );

  return {
    users: data,
    isLoading,
    isRefreshing: isValidating && data,
    error,
  };
}

// SWR Mutation
function useCreateUser() {
  return useSWRMutation(
    '/api/users',
    async (url: string, { arg }: { arg: CreateUserInput }) => {
      const res = await fetch(url, {
        method: 'POST',
        body: JSON.stringify(arg),
      });
      return res.json();
    },
    {
      onSuccess: () => mutate('/api/users'),
    }
  );
}
```

## State Management

### Zustand (Recommended for most cases)

```typescript
import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

interface AuthState {
  user: User | null;
  token: string | null;
  isAuthenticated: boolean;
  login: (credentials: Credentials) => Promise<void>;
  logout: () => void;
  updateUser: (updates: Partial<User>) => void;
}

const useAuthStore = create<AuthState>()(
  devtools(
    persist(
      immer((set, get) => ({
        user: null,
        token: null,
        isAuthenticated: false,

        login: async (credentials) => {
          const response = await api.auth.login(credentials);
          set((state) => {
            state.user = response.user;
            state.token = response.token;
            state.isAuthenticated = true;
          });
        },

        logout: () => {
          set((state) => {
            state.user = null;
            state.token = null;
            state.isAuthenticated = false;
          });
        },

        updateUser: (updates) => {
          set((state) => {
            if (state.user) {
              Object.assign(state.user, updates);
            }
          });
        },
      })),
      { name: 'auth-store' }
    ),
    { name: 'Auth' }
  )
);

// Selectors (prevent unnecessary re-renders)
const useUser = () => useAuthStore((state) => state.user);
const useIsAuthenticated = () => useAuthStore((state) => state.isAuthenticated);
```

### Redux Toolkit (Enterprise)

```typescript
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';

// Async thunk
export const fetchUsers = createAsyncThunk(
  'users/fetchAll',
  async (params: { page: number }, { rejectWithValue }) => {
    try {
      return await api.users.list(params);
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

// Slice
const usersSlice = createSlice({
  name: 'users',
  initialState: {
    items: [] as User[],
    status: 'idle' as 'idle' | 'loading' | 'succeeded' | 'failed',
    error: null as string | null,
    pagination: { page: 1, total: 0 },
  },
  reducers: {
    userAdded: (state, action: PayloadAction<User>) => {
      state.items.push(action.payload);
    },
    userUpdated: (state, action: PayloadAction<User>) => {
      const index = state.items.findIndex((u) => u.id === action.payload.id);
      if (index !== -1) {
        state.items[index] = action.payload;
      }
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUsers.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(fetchUsers.fulfilled, (state, action) => {
        state.status = 'succeeded';
        state.items = action.payload.data;
        state.pagination = action.payload.pagination;
      })
      .addCase(fetchUsers.rejected, (state, action) => {
        state.status = 'failed';
        state.error = action.payload as string;
      });
  },
});

export const { userAdded, userUpdated } = usersSlice.actions;
export default usersSlice.reducer;
```

## Error Handling

```typescript
// Custom error class
class ApiError extends Error {
  constructor(
    public response: Response,
    public data?: { type: string; title: string; detail?: string }
  ) {
    super(data?.title || 'API Error');
    this.name = 'ApiError';
  }

  static async fromResponse(response: Response): Promise<ApiError> {
    const data = await response.json().catch(() => null);
    return new ApiError(response, data);
  }
}

// Error boundary component
function QueryErrorBoundary({ children }: { children: React.ReactNode }) {
  const queryClient = useQueryClient();

  return (
    <QueryErrorResetBoundary>
      {({ reset }) => (
        <ErrorBoundary
          onReset={reset}
          fallbackRender={({ error, resetErrorBoundary }) => (
            <div className="error-container">
              <h2>Something went wrong</h2>
              <p>{error.message}</p>
              <button onClick={resetErrorBoundary}>Try again</button>
            </div>
          )}
        >
          {children}
        </ErrorBoundary>
      )}
    </QueryErrorResetBoundary>
  );
}

// Hook with error handling
function useUsersSafe(page: number) {
  const query = useUsers(page);

  useEffect(() => {
    if (query.error instanceof ApiError) {
      if (query.error.response.status === 401) {
        // Redirect to login
        router.push('/login');
      } else if (query.error.response.status >= 500) {
        // Show toast
        toast.error('Server error. Please try again later.');
      }
    }
  }, [query.error]);

  return query;
}
```

## Optimistic Updates Pattern

```typescript
function useTodoToggle() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (todo: Todo) =>
      api.todos.update(todo.id, { completed: !todo.completed }),

    onMutate: async (todo) => {
      await queryClient.cancelQueries({ queryKey: ['todos'] });

      const previous = queryClient.getQueryData<Todo[]>(['todos']);

      queryClient.setQueryData<Todo[]>(['todos'], (old) =>
        old?.map((t) =>
          t.id === todo.id ? { ...t, completed: !t.completed } : t
        )
      );

      return { previous };
    },

    onError: (err, todo, context) => {
      queryClient.setQueryData(['todos'], context?.previous);
      toast.error('Failed to update todo');
    },

    onSettled: () => {
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });
}
```

---

## Unit Test Template

```typescript
import { describe, it, expect, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const createWrapper = () => {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
};

describe('Frontend Patterns', () => {
  describe('useUsers hook', () => {
    it('should fetch and return users', async () => {
      vi.spyOn(global, 'fetch').mockResolvedValue({
        ok: true,
        json: async () => ({ data: [{ id: '1', name: 'Test' }] }),
      } as Response);

      const { result } = renderHook(() => useUsers(1), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isSuccess).toBe(true));
      expect(result.current.data?.data).toHaveLength(1);
    });

    it('should handle errors', async () => {
      vi.spyOn(global, 'fetch').mockResolvedValue({
        ok: false,
        status: 500,
      } as Response);

      const { result } = renderHook(() => useUsers(1), {
        wrapper: createWrapper(),
      });

      await waitFor(() => expect(result.current.isError).toBe(true));
    });
  });

  describe('Zustand store', () => {
    it('should update auth state on login', async () => {
      const { result } = renderHook(() => useAuthStore());

      await result.current.login({ email: 'test@test.com', password: 'pass' });

      expect(result.current.isAuthenticated).toBe(true);
      expect(result.current.user).toBeDefined();
    });

    it('should clear state on logout', () => {
      const { result } = renderHook(() => useAuthStore());

      result.current.logout();

      expect(result.current.isAuthenticated).toBe(false);
      expect(result.current.user).toBeNull();
    });
  });
});
```

---

## Troubleshooting

| Issue | Cause | Solution |
|-------|-------|----------|
| Infinite refetching | Missing dependency array | Use stable queryKey |
| Stale data shown | staleTime too high | Reduce staleTime or invalidate |
| Memory leak | Unmounted component | Use cleanup in useEffect |
| Too many re-renders | Non-memoized selectors | Use shallow comparison |
| Optimistic rollback fails | Missing previous snapshot | Always capture previous state |

---

## Quality Checklist

- [ ] Data fetching with TanStack Query or SWR
- [ ] Type-safe API client
- [ ] Error boundaries configured
- [ ] Loading states handled
- [ ] Optimistic updates for mutations
- [ ] Cache invalidation strategy
- [ ] State persistence (where needed)
- [ ] Memoization applied
- [ ] Tests for hooks and stores

Related Skills

frontend_mastery

16
from diegosouzapw/awesome-omni-skill

Advanced React patterns, performance optimization, and state management rules.

frontend

16
from diegosouzapw/awesome-omni-skill

Apply distinctive frontend design to React + TailwindCSS apps. Use when building UI components, pages, or improving visual design. Breaks generic "AI slop" patterns.

frontend-web-dev-expert

16
from diegosouzapw/awesome-omni-skill

Advanced frontend web development expert system that provides comprehensive modern web development services including architecture design, UI/UX implementation, performance optimization, engineering setup, and cross-platform development through expert collaboration and intelligent tool integration.

frontend-ui-tailwind-standards

16
from diegosouzapw/awesome-omni-skill

Standardized guidelines and patterns for Frontend Ui Tailwind Standards.

frontend-ui

16
from diegosouzapw/awesome-omni-skill

Create aesthetically pleasing, visually distinctive frontend UIs using research-backed prompting strategies from Anthropic's frontend aesthetics cookbook

frontend-ui-dark-ts

16
from diegosouzapw/awesome-omni-skill

Build dark-themed React applications using Tailwind CSS with custom theming, glassmorphism effects, and Framer Motion animations. Use when creating dashboards, admin panels, or data-rich interfaces...

frontend-ui-animator

16
from diegosouzapw/awesome-omni-skill

Analyze and implement purposeful UI animations for Next.js + Tailwind + React projects. Use when user asks to add animations, enhance UI motion, animate pages/components, or improve visual feedback. Triggers on "add animations", "animate UI", "motion design", "hover effects", "scroll animations", "page transitions", "micro-interactions".

frontend-svelte

16
from diegosouzapw/awesome-omni-skill

Technical knowledge for SvelteKit 5 development. Build reactive applications with Svelte's compile-time magic. Expert in SvelteKit, stores, and reactive patterns. Activate for Svelte development, performance optimization, or modern web apps. This skill provides MCP usage patterns and Svelte 5 conventions. Use when implementing Svelte components or SvelteKit routes.

frontend-specialist

16
from diegosouzapw/awesome-omni-skill

Master of UI/UX, React, TypeScript, and modern CSS.

frontend-slides

16
from diegosouzapw/awesome-omni-skill

Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides for a...

frontend-shadcn

16
from diegosouzapw/awesome-omni-skill

Frontend development using Vite + React + shadcn/ui + Tailwind CSS + React Router v7. Use when creating new frontend projects, adding UI components, implementing routing, styling with Tailwind, or working with shadcn/ui component library.

frontend-security-coder

16
from diegosouzapw/awesome-omni-skill

Expert in secure frontend coding practices specializing in XSS prevention, output sanitization, and client-side security patterns.