React performance
Skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack/plugins/devtools-pack/skills/react-performance
A curated pack of custom Claude Code skills for developers — installable as a Claude Code plugin marketplace.
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill react-performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
When to activate: React performance optimization, bundle size, code splitting, lazy loading, profiler, memoization, concurrent features
SKILL.md
5.5 KB, as published. Nobody here has run it
React Performance Patterns
Profiling First
// React DevTools Profiler — always profile before optimizing
// Enable in DevTools → Profiler → Record
// Code profiling with React Profiler API
import { Profiler, type ProfilerOnRenderCallback } from 'react'
const onRender: ProfilerOnRenderCallback = (id, phase, actualDuration) => {
if (actualDuration > 16) { // > 1 frame
console.warn(`Slow render: ${id} (${phase}) took ${actualDuration.toFixed(1)}ms`)
}
}
<Profiler id="Dashboard" onRender={onRender}>
<Dashboard />
</Profiler>
Memoization
When to use memo / useMemo / useCallback
// memo: stable component with expensive render and stable-ish props
const ExpensiveChart = memo(function Chart({ data }: { data: DataPoint[] }) {
// renders a big SVG chart
return <svg>...</svg>
}, (prev, next) => prev.data === next.data)
// useMemo: CPU-expensive pure computation (not just "avoid re-render")
const sortedItems = useMemo(
() => [...items].sort((a, b) => a.priority - b.priority),
[items] // only re-sort when items changes
)
// useCallback: stable function ref passed to memoized child or used as dep
const handleSelect = useCallback((id: string) => {
setSelected(id)
onSelect(id)
}, [onSelect]) // onSelect itself needs to be stable
Anti-patterns
// BAD: memoizing cheap computations (overhead > savings)
const name = useMemo(() => user.firstName + ' ' + user.lastName, [user])
// BAD: memo on component that receives new object literals every render
function Parent() {
return <Child style={{ color: 'red' }} /> // new object each render
}
// GOOD: extract stable values
const style = { color: 'red' } // module-level constant
function Parent() { return <Child style={style} /> }
State Colocation
// BAD: state too high — re-renders entire tree on every keystroke
function App() {
const [search, setSearch] = useState('') // re-renders App + all children
return <><SearchBox value={search} onChange={setSearch} /><HeavyList /></>
}
// GOOD: colocate state with the components that need it
function SearchBox() {
const [search, setSearch] = useState('') // only SearchBox re-renders
return <input value={search} onChange={e => setSearch(e.target.value)} />
}
Context Splitting
// BAD: one context causes all consumers to re-render on any change
const AppContext = createContext({ user, theme, cart, notifications })
// GOOD: split contexts by change frequency
const UserContext = createContext<User | null>(null)
const ThemeContext = createContext<Theme>('light')
const CartContext = createContext<Cart>({ items: [] })
Lazy Loading
import { lazy, Suspense, startTransition } from 'react'
const HeavyEditor = lazy(() => import('./HeavyEditor'))
function App() {
const [showEditor, setShowEditor] = useState(false)
// startTransition: mark as non-urgent (don't block urgent UI updates)
const openEditor = () => startTransition(() => setShowEditor(true))
return (
<>
<button onClick={openEditor}>Open Editor</button>
{showEditor && (
<Suspense fallback={<EditorSkeleton />}>
<HeavyEditor />
</Suspense>
)}
</>
)
}
Concurrent Features
// useDeferredValue: defer non-urgent rendering
function SearchResults({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query)
const isStale = query !== deferredQuery
return (
<div style={{ opacity: isStale ? 0.5 : 1 }}>
<Suspense fallback={<Spinner />}>
<Results query={deferredQuery} />
</Suspense>
</div>
)
}
// useTransition: mark state update as non-urgent
function TabBar() {
const [isPending, startTransition] = useTransition()
const [tab, setTab] = useState('home')
return (
<>
{tabs.map(t => (
<button
key={t}
onClick={() => startTransition(() => setTab(t))}
disabled={isPending}
>
{t}
</button>
))}
<Suspense fallback={<Spinner />}>
<TabContent tab={tab} />
</Suspense>
</>
)
}
Bundle Optimization
// Dynamic import for conditional heavy deps
async function exportToPDF() {
const { jsPDF } = await import('jspdf') // loaded only when needed
const doc = new jsPDF()
doc.save('report.pdf')
}
// Tree-shaking: named imports from specific sub-paths
import { format } from 'date-fns/format' // not 'date-fns'
import { debounce } from 'lodash-es/debounce' // not 'lodash'
Image & Asset Performance
// next/image with blur placeholder
import Image from 'next/image'
<Image
src={product.imageUrl}
alt={product.name}
width={400}
height={300}
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 400px"
placeholder="blur"
blurDataURL={product.blurHash}
/>
Keys & Reconciliation
// BAD: index as key causes incorrect reconciliation on reorder/insert
{items.map((item, i) => <Item key={i} item={item} />)}
// GOOD: stable unique ID
{items.map(item => <Item key={item.id} item={item} />)}
// Intentional reset via key (new key = new component instance)
<ExpensiveForm key={userId} userId={userId} />