Files
asepharyana-hub-guide/skills/react-frontend/SKILL.md
T

6.4 KiB

name, description
name description
react-frontend Use when building React components — hooks, state management, TanStack Query, React Router, performance patterns, and testing. Triggers from .tsx/.jsx files and framework file patterns.

React Frontend Best Practices

Component Patterns

Composition over Inheritance

// ✅ Prefer composition
function Layout({ sidebar, children }: { sidebar: ReactNode; children: ReactNode }) {
  return <div className="layout">{sidebar}<main>{children}</main></div>;
}

// ❌ Avoid inheritance patterns in React

Container/Presentational Separation

// Container: manages state, data fetching, business logic
function UserProfileContainer() {
  const { data: user } = useUserQuery(userId);
  const { mutate: update } = useUpdateUserMutation();
  return <UserProfile user={user!} onUpdate={update} />;
}

// Presentational: pure rendering, props-only
function UserProfile({ user, onUpdate }: UserProfileProps) {
  return <div>{user.name} <button onClick={onUpdate}>Edit</button></div>;
}

Custom Hooks for Logic Extraction

// Extract reusable logic into custom hooks
function useUserPermissions(userId: string) {
  const { data: user } = useUserQuery(userId);
  return useMemo(() => ({
    isAdmin: user?.role === 'admin',
    canEdit: user?.role === 'admin' || user?.role === 'editor',
    canDelete: user?.role === 'admin',
  }), [user]);
}

Hooks Rules

  • Only call hooks at the top level — not in conditions, loops, or callbacks.
  • Only call hooks from React functions — component or custom hook.
  • Deps array matches reality — include all values used inside.
  • useMemo for expensive computations. useCallback for stable references.
  • useEffect is for synchronization, not lifecycle. If you can compute from state, do it.
// ❌ Unnecessary effect
const [fullName, setFullName] = useState('');
useEffect(() => { setFullName(`${first} ${last}`); }, [first, last]);

// ✅ Derived state
const fullName = `${first} ${last}`;

Data Fetching (TanStack Query)

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

// Query
function useUserQuery(id: string) {
  return useQuery({
    queryKey: ['users', id],
    queryFn: () => api.users.get({ params: { id } }),
    staleTime: 30_000,  // 30s before refetch
    gcTime: 5 * 60_000, // 5min cache
  });
}

// Mutation with optimistic update
function useUpdateUserMutation() {
  const queryClient = useQueryClient();
  return useMutation({
    mutationFn: (data: UpdateUserInput) => api.users.update({ body: data }),
    onMutate: async (newUser) => {
      await queryClient.cancelQueries({ queryKey: ['users', newUser.id] });
      const previous = queryClient.getQueryData(['users', newUser.id]);
      queryClient.setQueryData(['users', newUser.id], newUser);
      return { previous };
    },
    onError: (_, __, context) => {
      queryClient.setQueryData(['users', context.previous.id], context.previous);
    },
    onSettled: () => queryClient.invalidateQueries({ queryKey: ['users'] }),
  });
}

Rules:

  • staleTime for read-through caching. Default 0 = refetch on mount.
  • gcTime for garbage collection of unused data.
  • onMutate/onError/onSettled for optimistic updates.
  • Queries over custom fetch + useEffect in all cases.

State Management Selection

Need Solution
Server state TanStack Query
URL state React Router / TanStack Router
Form state React Hook Form + Zod
Client state (global) Zustand or Context
Client state (local) useState / useReducer
Component communication Props / lifting state up
// Zustand — lightweight, no boilerplate
import { create } from 'zustand';

interface UIStore {
  sidebarOpen: boolean;
  toggleSidebar: () => void;
}
const useUIStore = create<UIStore>((set) => ({
  sidebarOpen: true,
  toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));

Routing (TanStack Router / React Router)

// TanStack Router — type-safe, modern
const router = createRouter({
  routeTree: rootRoute.addChildren([
    indexRoute,
    usersRoute.addChildren([userRoute, userProfileRoute]),
  ]),
});
  • File-based routing (Next.js App Router, Vite/Router) for simpler projects.
  • Type-safe routers (TanStack Router) for larger apps.
  • Lazy load route components — React.lazy(() => import('./routes/Dashboard')).

Performance

  • Virtual lists@tanstack/react-virtual for 100+ items.
  • React.memo sparingly — only for components that re-render often with same props.
  • useMemo for expensive calculations — not for every value.
  • Code splitting — per route, per heavy component.
  • Bundle analysisvite-bundle-visualizer to find bloat.

Testing (Vitest + Testing Library)

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserProfile } from './UserProfile';

describe('UserProfile', () => {
  it('renders user name', () => {
    render(<UserProfile user={{ name: 'Alice' }} />);
    expect(screen.getByText('Alice')).toBeInTheDocument();
  });

  it('calls onUpdate when edit clicked', async () => {
    const onUpdate = vi.fn();
    render(<UserProfile user={{ name: 'Alice' }} onUpdate={onUpdate} />);
    await userEvent.click(screen.getByRole('button', { name: /edit/i }));
    expect(onUpdate).toHaveBeenCalledTimes(1);
  });
});
  • Testing Library for user-centric tests. Never test implementation details.
  • userEvent over fireEvent — simulates real user interactions.
  • Component-level tests for behavior, not storybook-style visual tests here.

CSS / Styling

  • Tailwind CSS for utility-first styling. Consistent, fast, small.
  • CSS Modules when you need scoped component styles.
  • CSS-in-JS (styled-components, emotion) — only for dynamic theming. Prefer Tailwind.

Anti-patterns

  • useEffect for data fetching — use TanStack Query
  • Prop drilling beyond 3 levels — compose or context
  • useState for derived data — compute from existing state
  • Direct DOM manipulation — use React refs
  • any in component props — always type props
  • Large component files — split by responsibility
  • index as key — breaks reconciliation on reorder
  • useEffect without deps — runs every render