Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Use when creating UI components in React - functional components, hooks, custom hooks, or component composition patterns. NOT when backend logic, API routes, or non-React frameworks are involved. Triggers: "create component", "build widget", "KPI card", "form", "modal", "custom hook", "useContext", "useState", "useEffect".
.claude/skills/aiskillstore-react-component/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-05 | ✓→✗ | ▼ Worse | 104% | 0% |
| case-09 | ✓→✗ | ▼ Worse | 134% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 78% | 0% |
| case-06 | ✓→✓ | = Same ✓ | 80% | 0% |
Expert guidance for building React functional components, hooks, and composition patterns. Focuses on TypeScript, performance, accessibility, and modern React best practices.
This skill triggers when users request:
typescript// ✅ GOOD: Functional component with TypeScript interface StudentKPICardProps { studentName: string; attendance: number; loading?: boolean; } export const StudentKPICard = React.memo(({ studentName, attendance, loading = false }: StudentKPICardProps) => { const [isHovered, setIsHovered] = useState(false); if (loading) return <LoadingSkeleton />; return <div>{/* content */}</div>; });
Requirements:
React.memo for components receiving same props frequentlyReact.forwardRef when ref forwarding is neededtypescript// ✅ GOOD: Proper hook usage with cleanup export const AttendanceMonitor = ({ studentId }: { studentId: string }) => { const [data, setData] = useState(null); const [error, setError] = useState<string | null>(null); useEffect(() => { let isMounted = true; const fetchAttendance = async () => { try { const result = await api.getAttendance(studentId); if (isMounted) setData(result); } catch (err) { if (isMounted) setError(err.message); } }; fetchAttendance(); return () => { isMounted = false; }; }, [studentId]); return /* JSX */; };
Requirements:
useState: For local component state onlyuseEffect: For side-effects with proper cleanup functionsuseContext: For global state (theme, auth, language)useCallback/useMemo for expensive operations in liststypescript// ✅ GOOD: Custom hook with proper types interface UseAttendanceResult { data: AttendanceData | null; loading: boolean; error: string | null; refetch: () => Promise<void>; } export const useAttendance = (studentId: string): UseAttendanceResult => { const [data, setData] = useState<AttendanceData | null>(null); const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); const fetchAttendance = useCallback(async () => { setLoading(true); try { const result = await api.getAttendance(studentId); setData(result); setError(null); } catch (err) { setError(err.message); } finally { setLoading(false); } }, [studentId]); useEffect(() => { fetchAttendance(); }, [fetchAttendance]); return { data, loading, error, refetch: fetchAttendance }; };
Requirements:
use prefixtypescript// ✅ GOOD: Composition via children prop interface CardProps { title: string; children: React.ReactNode; footer?: React.ReactNode; } export const Card = ({ title, children, footer }: CardProps) => ( <div className="card"> <h2>{title}</h2> <div className="card-content">{children}</div> {footer && <div className="card-footer">{footer}</div>} </div> ); // Usage <Card title="Student Info" footer={<Button>Save</Button>}> <StudentDetails /> </Card> // ✅ GOOD: Higher-order component pattern export const withLoading = <P extends object>(WrappedComponent: React.ComponentType<P>) => { return (props: P & { loading?: boolean }) => { const { loading = false, ...rest } = props; if (loading) return <LoadingSpinner />; return <WrappedComponent {...(rest as P)} />; }; };
Requirements:
children prop for flexible contentComponentName.tsx):ComponentName.test.tsx):ComponentName.stories.tsx):?Before completing any component:
any typesuseCallback/useMemo for expensive operations in liststypescriptexport const StudentList = () => { const { data: students, loading, error } = useStudents(); if (loading) return <LoadingSkeleton count={5} />; if (error) return <ErrorState message={error} />; return ( <ul> {students?.map((student) => ( <StudentItem key={student.id} student={student} /> ))} </ul> ); };
typescriptinterface FormValues { name: string; email: string; } export const StudentForm = () => { const { register, handleSubmit, formState: { errors } } = useForm<FormValues>(); const onSubmit = async (data: FormValues) => { await api.createStudent(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Input {...register('name', { required: true })} error={errors.name} /> <Input {...register('email', { required: true })} error={errors.email} /> <Button type="submit">Save</Button> </form> ); };
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→fail | 43,823 | 32,357 | -26% | 1 | 1 | 0% | 8,235 | 9,485 | +15% | 0 | 0 | — |
case-04 | pass→pass | 16,195 | 23,630 | +46% | 1 | 1 | 0% | 3,357 | 5,983 | +78% | 0 | 0 | — |
case-01 | fail→fail | 33,894 | 34,089 | +1% | 1 | 1 | 0% | 6,247 | 8,353 | +34% | 0 | 0 | — |
case-02 | fail→fail | 37,111 | 35,990 | -3% | 1 | 1 | 0% | 8,236 | 9,121 | +11% | 0 | 0 | — |
case-05 | pass→fail | 23,608 | 29,559 | +25% | 1 | 1 | 0% | 3,590 | 7,313 | +104% | 0 | 0 | — |
case-06 | pass→pass | 18,312 | 24,962 | +36% | 1 | 1 | 0% | 2,649 | 4,779 | +80% | 0 | 0 | — |
case-07 | pass→pass | 16,436 | 24,519 | +49% | 1 | 1 | 0% | 3,234 | 7,619 | +136% | 0 | 0 | — |
case-08 | pass→pass | 15,912 | 23,619 | +48% | 1 | 1 | 0% | 2,953 | 7,275 | +146% | 0 | 0 | — |
case-09 | pass→fail | 15,871 | 25,281 | +59% | 1 | 1 | 0% | 3,295 | 7,723 | +134% | 0 | 0 | — |
case-18 | pass→pass | 25,755 | 33,558 | +30% | 1 | 1 | 0% | 4,462 | 8,800 | +97% | 0 | 0 | — |
case-10 | pass→pass | 24,330 | 21,897 | -10% | 1 | 1 | 0% | 4,055 | 6,955 | +72% | 0 | 0 | — |
case-11 | pass→pass | 18,883 | 17,275 | -9% | 1 | 1 | 0% | 2,435 | 5,547 | +128% | 0 | 0 | — |
case-12 | pass→pass | 19,173 | 21,688 | +13% | 1 | 1 | 0% | 2,760 | 5,658 | +105% | 0 | 0 | — |
case-13 | pass→pass | 10,713 | 24,862 | +132% | 1 | 1 | 0% | 2,222 | 6,587 | +196% | 0 | 0 | — |
case-14 | pass→pass | 23,265 | 24,724 | +6% | 1 | 1 | 0% | 3,304 | 6,434 | +95% | 0 | 0 | — |
case-15 | fail→fail | 51,377 | 23,484 | -54% | 1 | 1 | 0% | 5,451 | 7,387 | +36% | 0 | 0 | — |
case-16 | fail→fail | 26,831 | 32,855 | +22% | 1 | 1 | 0% | 4,545 | 8,347 | +84% | 0 | 0 | — |
case-17 | fail→fail | 26,334 | 28,694 | +9% | 1 | 1 | 0% | 4,273 | 7,208 | +69% | 0 | 0 | — |
case-19 | pass→pass | 34,459 | 25,754 | -25% | 1 | 1 | 0% | 3,471 | 5,468 | +58% | 0 | 0 | — |
case-20 | fail→pass | 35,981 | 57,754 | +61% | 1 | 1 | 0% | 8,217 | 9,853 | +20% | 0 | 0 | — |
case-21 | pass→pass | 19,025 | 23,452 | +23% | 1 | 1 | 0% | 2,621 | 5,998 | +129% | 0 | 0 | — |
case-22 | pass→pass | 20,388 | 26,868 | +32% | 1 | 1 | 0% | 3,251 | 6,945 | +114% | 0 | 0 | — |
case-23 | pass→pass | 18,558 | 24,149 | +30% | 1 | 1 | 0% | 2,789 | 6,211 | +123% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of -17 percentage points is the difference between those two pass rates over the 23 comparable cases. 2 cases got worse with the skill loaded, and they are included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.