agentsclimarketplace

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.

Install
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill react-performance

Assembled 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} />

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.