react-frontend-expert

安装量: 69
排名: #11077

安装

npx skills add https://github.com/hieutrtr/ai1-skills --skill react-frontend-expert

React Frontend Expert When to Use Activate this skill when: Creating or modifying React components (functional components only) Writing custom hooks ( useXxx ) Building pages with routing Implementing data fetching with TanStack Query Handling forms with validation Setting up project structure for a React/TypeScript application Do NOT use this skill for: Writing component or hook tests (use react-testing-patterns ) E2E browser testing (use e2e-testing ) API contract design (use api-design-patterns ) Backend implementation (use python-backend-expert ) Deployment or CI/CD (use deployment-pipeline ) Instructions Project Structure src/ ├── api/ # API client functions and query options │ ├── client.ts # Axios/fetch instance with interceptors │ ├── users.ts # User API functions + query options │ └── posts.ts ├── components/ # Shared, reusable UI components │ ├── Button.tsx │ ├── Modal.tsx │ ├── Table/ │ │ ├── Table.tsx │ │ └── TablePagination.tsx │ └── Form/ │ ├── Input.tsx │ └── Select.tsx ├── features/ # Domain-specific feature components │ ├── users/ │ │ ├── UserList.tsx │ │ └── UserProfile.tsx │ └── posts/ │ └── PostEditor.tsx ├── hooks/ # Custom hooks │ ├── useAuth.ts │ ├── useDebounce.ts │ └── usePagination.ts ├── layouts/ # Layout components │ ├── MainLayout.tsx │ └── AuthLayout.tsx ├── pages/ # Route-level page components │ ├── HomePage.tsx │ ├── LoginPage.tsx │ └── users/ │ ├── UserListPage.tsx │ └── UserDetailPage.tsx ├── types/ # Shared TypeScript types │ ├── api.ts # API response types │ └── user.ts ├── App.tsx # Root component with providers and router └── main.tsx # Entry point Component Structure Functional Components Only interface UserCardProps { user : User ; onEdit : ( userId : number ) => void ; showEmail ? : boolean ; } export function UserCard ( { user , onEdit , showEmail = false } : UserCardProps ) { return ( < article className = " user-card "

< h3

{ user . displayName } </ h3

{ showEmail && < p

{ user . email } </ p

} < button type = " button " onClick = { ( ) => onEdit ( user . id ) }

Edit </ button

</ article

) ; } Component rules: Named exports for shared components: export function Button Default exports for page components: export default function UserListPage Props interface named {Component}Props Destructure props in function signature Keep components under 200 lines — extract sub-components or hooks when larger Use children and composition over deep prop drilling Never use React.FC — use plain function syntax Component File Organization For complex components, co-locate related files: UserProfile/ ├── UserProfile.tsx # Main component ├── UserProfile.css # Styles (or .module.css) ├── UserAvatar.tsx # Sub-component └── index.ts # Re-export: export { UserProfile } from './UserProfile' Hooks Rules and Custom Hooks Rules of Hooks Only call hooks at the top level — never inside loops, conditions, or nested functions Only call hooks from React function components or custom hooks Custom hooks must start with use Custom Hook Patterns useDebounce: export function useDebounce < T

( value : T , delayMs : number ) : T { const [ debouncedValue , setDebouncedValue ] = useState ( value ) ; useEffect ( ( ) => { const timer = setTimeout ( ( ) => setDebouncedValue ( value ) , delayMs ) ; return ( ) => clearTimeout ( timer ) ; } , [ value , delayMs ] ) ; return debouncedValue ; } useAuth: interface AuthContext { user : User | null ; isAuthenticated : boolean ; login : ( credentials : LoginCredentials ) => Promise < void

; logout : ( ) => void ; } const AuthContext = createContext < AuthContext | null

( null ) ; export function useAuth ( ) : AuthContext { const context = useContext ( AuthContext ) ; if ( ! context ) { throw new Error ( "useAuth must be used within AuthProvider" ) ; } return context ; } usePagination: interface PaginationState { cursor : string | null ; hasMore : boolean ; goToNext : ( nextCursor : string ) => void ; reset : ( ) => void ; } export function usePagination ( ) : PaginationState { const [ cursor , setCursor ] = useState < string | null

( null ) ; const [ hasMore , setHasMore ] = useState ( true ) ; return { cursor , hasMore , goToNext : ( nextCursor : string ) => { setCursor ( nextCursor ) ; } , reset : ( ) => { setCursor ( null ) ; setHasMore ( true ) ; } , } ; } When to extract a custom hook: Logic is reused across 2+ components Component has complex state management (>3 useState calls) Side effects need encapsulation (subscriptions, timers) Data fetching logic can be shared Data Fetching with TanStack Query Query Options Factory (Recommended) Centralize query key and function definitions to prevent key collisions: // api/users.ts import { queryOptions } from "@tanstack/react-query" ; export const userQueries = { all : ( ) => queryOptions ( { queryKey : [ "users" ] , queryFn : ( ) => apiClient . get < UserListResponse

( "/users" ) , } ) , detail : ( userId : number ) => queryOptions ( { queryKey : [ "users" , userId ] , queryFn : ( ) => apiClient . get < UserResponse

( /users/ ${ userId } ) , } ) , search : ( query : string ) => queryOptions ( { queryKey : [ "users" , "search" , query ] , queryFn : ( ) => apiClient . get < UserListResponse

( /users?q= ${ query } ) , enabled : query . length

0 , } ) , } ; Using Queries in Components export function UserDetailPage ( { userId } : { userId : number } ) { const { data : user , isPending , isError , error } = useQuery ( userQueries . detail ( userId ) ) ; if ( isPending ) return < Spinner /> ; if ( isError ) return < ErrorMessage error = { error } /> ; return < UserProfile user = { user } /> ; } Mutations with Cache Invalidation export function useCreateUser ( ) { const queryClient = useQueryClient ( ) ; return useMutation ( { mutationFn : ( data : UserCreate ) => apiClient . post < UserResponse

( "/users" , data ) , onSuccess : ( ) => { queryClient . invalidateQueries ( { queryKey : [ "users" ] } ) ; } , } ) ; } TanStack Query rules: Set staleTime 0 (default 0 is too aggressive): staleTime: 5 * 60 * 1000 (5 min) Use invalidateQueries() after mutations — never manual refetch() Handle all states: isPending , isError , data Use queryOptions() factory — prevents key typos and duplication Use enabled to prevent queries from running with incomplete parameters TypeScript Conventions // Use interface for object shapes (components props, API responses) interface User { id : number ; email : string ; displayName : string ; role : "admin" | "editor" | "member" ; } // Use type for unions, intersections, and computed types type UserRole = User [ "role" ] ; type CreateOrUpdate = UserCreate | UserUpdate ; // Discriminated unions for state machines type AsyncState < T

= | { status : "idle" } | { status : "loading" } | { status : "success" ; data : T } | { status : "error" ; error : Error } ; TypeScript rules: Enable strict: true in tsconfig.json — no exceptions Never use any — use unknown for truly unknown types Use as const for literal object types Prefer interface for extensible types, type for everything else Use generics for reusable utility types and hooks Export types from types/ directory for shared use Form Handling import { useForm } from "react-hook-form" ; import { zodResolver } from "@hookform/resolvers/zod" ; import { z } from "zod" ; const userSchema = z . object ( { email : z . string ( ) . email ( "Invalid email" ) , displayName : z . string ( ) . min ( 1 , "Required" ) . max ( 100 ) , role : z . enum ( [ "admin" , "editor" , "member" ] ) , } ) ; type UserFormData = z . infer < typeof userSchema

; export function UserForm ( { onSubmit } : { onSubmit : ( data : UserFormData ) => void } ) { const { register , handleSubmit , formState : { errors , isSubmitting } , } = useForm < UserFormData

( { resolver : zodResolver ( userSchema ) , } ) ; return ( < form onSubmit = { handleSubmit ( onSubmit ) } noValidate

< label htmlFor = " email "

Email </ label

< input id = " email " type = " email " { ... register ( "email" ) } aria-invalid = { ! ! errors . email } /> { errors . email && < span role = " alert "

{ errors . email . message } </ span

} < label htmlFor = " displayName "

Name </ label

< input id = " displayName " { ... register ( "displayName" ) } aria-invalid = { ! ! errors . displayName } /> { errors . displayName && < span role = " alert "

{ errors . displayName . message } </ span

} < button type = " submit " disabled = { isSubmitting }

Save </ button

</ form

) ; } Accessibility Requirements Every component must meet WCAG 2.1 AA: Semantic HTML first: Use