Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Standardized guidelines and patterns for Frontend React Performance Optimization.
.claude/skills/valec3-frontend-react-performance-optimization/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | 57% | 0% |
| case-07 | ✓→✓ | = Same ✓ | 36% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 59% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 130% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 51% | 0% |
Prevent unnecessary re-renders of components:
typescript// Without memo - re-renders on every parent update export const ExpensiveComponent = ({ data }: { data: string }) => { console.log('Rendering ExpensiveComponent'); return <div>{data}</div>; }; // With memo - only re-renders when data changes export const ExpensiveComponent = React.memo(({ data }: { data: string }) => { console.log('Rendering ExpensiveComponent'); return <div>{data}</div>; }); // Custom comparison function export const UserCard = React.memo( ({ user }: { user: User }) => <div>{user.name}</div>, (prevProps, nextProps) => { // Only re-render if user.id changed return prevProps.user.id === nextProps.user.id; } );
Memoize expensive calculations:
typescriptfunction ProductList({ products, searchTerm }: Props) { // ❌ Bad - filters on every render const filtered = products.filter(p => p.name.includes(searchTerm) ); // ✅ Good - only recalculates when dependencies change const filteredProducts = useMemo(() => { console.log('Filtering products'); return products.filter(p => p.name.includes(searchTerm)); }, [products, searchTerm]); return <div>{filteredProducts.map(...)}</div>; }
Memoize function references:
typescriptfunction Parent() { const [count, setCount] = useState(0); const [other, setOther] = useState(0); // ❌ Bad - new function on every render const handleClick = () => { setCount(count + 1); }; // ✅ Good - same function reference const handleClick = useCallback(() => { setCount(c => c + 1); }, []); // Empty deps because we use functional update return <ExpensiveChild onClick={handleClick} />; } const ExpensiveChild = React.memo(({ onClick }: { onClick: () => void }) => { console.log('Child rendered'); return <button onClick={onClick}>Click</button>; });
Route-based splitting:
typescriptimport { lazy, Suspense } from 'react'; const Dashboard = lazy(() => import('./pages/Dashboard')); const Settings = lazy(() => import('./pages/Settings')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/dashboard" element={<Dashboard />} /> <Route path="/settings" element={<Settings />} /> </Routes> </Suspense> ); }
Component-based splitting:
typescriptconst HeavyChart = lazy(() => import('./components/HeavyChart')); function Analytics() { const [showChart, setShowChart] = useState(false); return ( <div> <button onClick={() => setShowChart(true)}>Show Chart</button> {showChart && ( <Suspense fallback={<Skeleton />}> <HeavyChart data={data} /> </Suspense> )} </div> ); }
For long lists, render only visible items:
typescriptimport { FixedSizeList } from 'react-window'; function VirtualizedList({ items }: { items: string[] }) { const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => ( <div style={style}> {items[index]} </div> ); return ( <FixedSizeList height={600} itemCount={items.length} itemSize={50} width="100%" > {Row} </FixedSizeList> ); }
typescript// Debounce: Wait for pause in events function SearchInput() { const [search, setSearch] = useState(''); const debouncedSearch = useMemo( () => debounce((value: string) => { // API call fetchResults(value); }, 500), [] ); useEffect(() => { debouncedSearch(search); }, [search, debouncedSearch]); return <input value={search} onChange={e => setSearch(e.target.value)} />; } // Throttle: Limit frequency function ScrollHandler() { const handleScroll = useCallback( throttle(() => { console.log('Scroll position:', window.scrollY); }, 200), [] ); useEffect(() => { window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [handleScroll]); return <div>Scroll me</div>; }
typescript// ❌ Bad - runs expensive function on every render const [data, setData] = useState(expensiveComputation()); // ✅ Good - only runs once const [data, setData] = useState(() => expensiveComputation()); // Example const [user, setUser] = useState(() => { const stored = localStorage.getItem('user'); return stored ? JSON.parse(stored) : null; });
typescript// ❌ Bad - using index as key {items.map((item, index) => ( <Item key={index} data={item} /> ))} // ✅ Good - using stable unique identifier {items.map(item => ( <Item key={item.id} data={item} /> ))}
Mark non-urgent updates:
typescriptfunction SearchResults() { const [isPending, startTransition] = useTransition(); const [search, setSearch] = useState(''); const [results, setResults] = useState([]); const handleSearch = (value: string) => { setSearch(value); // Urgent: update input immediately startTransition(() => { // Non-urgent: filter can be delayed setResults(filterResults(value)); }); }; return ( <> <input value={search} onChange={e => handleSearch(e.target.value)} /> {isPending ? <Spinner /> : <ResultsList results={results} />} </> ); }
memo for expensive componentsuseMemo for expensive calculationsuseCallback for stable references| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-07 | pass→pass | 12,190 | 9,054 | -26% | 1 | 1 | 0% | 2,625 | 3,579 | +36% | 0 | 0 | — |
case-01 | pass→pass | 8,611 | 5,522 | -36% | 1 | 1 | 0% | 1,688 | 2,692 | +59% | 0 | 0 | — |
case-02 | pass→pass | 5,800 | 3,934 | -32% | 1 | 1 | 0% | 1,084 | 2,496 | +130% | 0 | 0 | — |
case-03 | pass→pass | 13,442 | 10,969 | -18% | 1 | 1 | 0% | 2,501 | 3,766 | +51% | 0 | 0 | — |
case-04 | pass→pass | 8,057 | 4,621 | -43% | 1 | 1 | 0% | 1,567 | 2,611 | +67% | 0 | 0 | — |
case-05 | pass→pass | 10,707 | 6,224 | -42% | 1 | 1 | 0% | 1,916 | 2,840 | +48% | 0 | 0 | — |
case-06 | fail→pass | 11,535 | 6,977 | -40% | 1 | 1 | 0% | 2,038 | 3,200 | +57% | 0 | 0 | — |
case-08 | pass→pass | 11,668 | 10,036 | -14% | 1 | 1 | 0% | 2,008 | 3,648 | +82% | 0 | 0 | — |
case-09 | pass→pass | 4,418 | 2,839 | -36% | 1 | 1 | 0% | 795 | 2,226 | +180% | 0 | 0 | — |
case-10 | pass→pass | 9,521 | 7,005 | -26% | 1 | 1 | 0% | 1,767 | 2,990 | +69% | 0 | 0 | — |
case-11 | pass→pass | 10,492 | 8,099 | -23% | 1 | 1 | 0% | 2,101 | 3,185 | +52% | 0 | 0 | — |
case-12 | pass→pass | 4,554 | 4,293 | -6% | 1 | 1 | 0% | 685 | 2,277 | +232% | 0 | 0 | — |
case-13 | pass→pass | 9,434 | 5,282 | -44% | 1 | 1 | 0% | 1,667 | 2,657 | +59% | 0 | 0 | — |
case-14 | pass→pass | 6,664 | 3,507 | -47% | 1 | 1 | 0% | 1,297 | 2,339 | +80% | 0 | 0 | — |
case-15 | pass→pass | 14,827 | 10,601 | -29% | 1 | 1 | 0% | 2,048 | 3,275 | +60% | 0 | 0 | — |
case-16 | pass→pass | 2,351 | 2,837 | +21% | 1 | 1 | 0% | 430 | 2,236 | +420% | 0 | 0 | — |
case-17 | pass→pass | 3,254 | 2,888 | -11% | 1 | 1 | 0% | 599 | 2,215 | +270% | 0 | 0 | — |
case-18 | pass→pass | 10,039 | 6,939 | -31% | 1 | 1 | 0% | 1,597 | 2,980 | +87% | 0 | 0 | — |
case-19 | pass→pass | 7,540 | 5,037 | -33% | 1 | 1 | 0% | 1,292 | 2,409 | +86% | 0 | 0 | — |
case-20 | pass→pass | 10,396 | 9,890 | -5% | 1 | 1 | 0% | 2,182 | 3,939 | +81% | 0 | 0 | — |
case-21 | pass→pass | 5,305 | 4,272 | -19% | 1 | 1 | 0% | 1,066 | 2,535 | +138% | 0 | 0 | — |
case-22 | pass→pass | 11,935 | 11,809 | -1% | 1 | 1 | 0% | 2,451 | 4,271 | +74% | 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. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
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.