Expertise·Design & Media

Frontend Development Patterns

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.

You say
Buy it · $12 Read it before you buy $12 Written by affaan-m · unverified publisher
Context cost
3.9k tokensestimated from the bundle, loaded when it triggers
Bundle
1 file · 15.4 kBtext throughout, nothing executable
Licence
MITpaid listing
Last change
no release on file
Servers it uses
Noneruns standalone

What it does

Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. Use when building or reviewing React or Next.js components, state, or render performance.

Installed, it changes the agent in these ways.

What this skill changes about the agent is not written down here yet. The listing was collected from its source, and the description is in its own SKILL.md.

Expertise

Domain judgement the base model does not have.

designnextjsreactperformance
Filed under

Design & Media

The skill itself

This is the whole product. A skill is instructions the model reads, so there is nothing behind the listing you cannot see first — the front matter loads with every session, and the body below it loads when the skill triggers.

SKILL.md15.4 kB · 658 lines
--- name: frontend-patterns description: Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices. Use when building or reviewing React or Next.js components, state, or render performance. metadata: origin: ECC ---
8# Frontend Development Patterns
9
10Modern frontend patterns for React, Next.js, and performant user interfaces.
11
12## When to Activate
13
14- Building React components (composition, props, rendering)
15- Managing state (useState, useReducer, Zustand, Context)
16- Implementing data fetching (SWR, React Query, server components)
17- Optimizing performance (memoization, virtualization, code splitting)
18- Working with forms (validation, controlled inputs, Zod schemas)
19- Handling client-side routing and navigation
20- Building accessible, responsive UI patterns
21
22## Component Patterns
23
24### Composition Over Inheritance
25
26```typescript
27// PASS: GOOD: Component composition
28interface CardProps {
29 children: React.ReactNode
30 variant?: 'default' | 'outlined'
31}
32
33export function Card({ children, variant = 'default' }: CardProps) {
34 return <div className={card card-${variant}}>{children}</div>
35}
36
37export function CardHeader({ children }: { children: React.ReactNode }) {
38 return <div className="card-header">{children}</div>
39}
40
41export function CardBody({ children }: { children: React.ReactNode }) {
42 return <div className="card-body">{children}</div>
43}
44
45// Usage
46<Card>
47 <CardHeader>Title</CardHeader>
48 <CardBody>Content</CardBody>
49</Card>
50```
51
52### Compound Components
53
54```typescript
55interface TabsContextValue {
56 activeTab: string
57 setActiveTab: (tab: string) => void
58}
59
60const TabsContext = createContext<TabsContextValue | undefined>(undefined)
61
62export function Tabs({ children, defaultTab }: {
63 children: React.ReactNode
64 defaultTab: string
65}) {
66 const [activeTab, setActiveTab] = useState(defaultTab)
67
68 return (
69 <TabsContext.Provider value={{ activeTab, setActiveTab }}>
70 {children}
71 </TabsContext.Provider>
72 )
73}
74
75export function TabList({ children }: { children: React.ReactNode }) {
76 return <div className="tab-list">{children}</div>
77}
78
79export function Tab({ id, children }: { id: string, children: React.ReactNode }) {
80 const context = useContext(TabsContext)
81 if (!context) throw new Error('Tab must be used within Tabs')
82
83 return (
84 <button
85 className={context.activeTab === id ? 'active' : ''}
86 onClick={() => context.setActiveTab(id)}
87 >
88 {children}
89 </button>
90 )
91}
92
93// Usage
94<Tabs defaultTab="overview">
95 <TabList>
96 <Tab id="overview">Overview</Tab>
97 <Tab id="details">Details</Tab>
98 </TabList>
99</Tabs>
100```
101
102### Render Props Pattern
103
104```typescript
105interface DataLoaderProps<T> {
106 url: string
107 children: (data: T | null, loading: boolean, error: Error | null) => React.ReactNode
108}
109
110export function DataLoader<T>({ url, children }: DataLoaderProps<T>) {
111 const [data, setData] = useState<T | null>(null)
112 const [loading, setLoading] = useState(true)
113 const [error, setError] = useState<Error | null>(null)
114
115 useEffect(() => {
116 fetch(url)
117 .then(res => res.json())
118 .then(setData)
119 .catch(setError)
120 .finally(() => setLoading(false))
121 }, [url])
122
123 return <>{children(data, loading, error)}</>
124}
125
126// Usage
127<DataLoader<Market[]> url="/api/markets">
128 {(markets, loading, error) => {
129 if (loading) return <Spinner />
130 if (error) return <Error error={error} />
131 return <MarketList markets={markets!} />
132 }}
133</DataLoader>
134```
135
136## Custom Hooks Patterns
137
138### State Management Hook
139
140```typescript
141export function useToggle(initialValue = false): [boolean, () => void] {
142 const [value, setValue] = useState(initialValue)
143
144 const toggle = useCallback(() => {
145 setValue(v => !v)
146 }, [])
147
148 return [value, toggle]
149}
150
151// Usage
152const [isOpen, toggleOpen] = useToggle()
153```
154
155### Async Data Fetching Hook
156
157```typescript
158interface UseQueryOptions<T> {
159 onSuccess?: (data: T) => void
160 onError?: (error: Error) => void
161 enabled?: boolean
162}
163
164export function useQuery<T>(
165 key: string,
166 fetcher: () => Promise<T>,
167 options?: UseQueryOptions<T>
168) {
169 const [data, setData] = useState<T | null>(null)
170 const [error, setError] = useState<Error | null>(null)
171 const [loading, setLoading] = useState(false)
172
173 // Keep the latest fetcher/options in refs so refetch stays referentially
174 // stable even when callers pass inline functions and object literals.
175 // Without this, every render creates a new refetch, and the effect below
176 // re-runs after each state update - an infinite fetch loop.
177 const fetcherRef = useRef(fetcher)
178 const optionsRef = useRef(options)
179 useEffect(() => {
180 fetcherRef.current = fetcher
181 optionsRef.current = options
182 })
183
184 const refetch = useCallback(async () => {
185 setLoading(true)
186 setError(null)
187
188 try {
189 const result = await fetcherRef.current()
190 setData(result)
191 optionsRef.current?.onSuccess?.(result)
192 } catch (err) {
193 const error = err as Error
194 setError(error)
195 optionsRef.current?.onError?.(error)
196 } finally {
197 setLoading(false)
198 }
199 }, [])
200
201 const enabled = options?.enabled !== false
202
203 useEffect(() => {
204 if (enabled) {
205 refetch()
206 }
207 }, [key, enabled, refetch])
208
209 return { data, error, loading, refetch }
210}
211
212// Usage
213const { data: markets, loading, error, refetch } = useQuery(
214 'markets',
215 () => fetch('/api/markets').then(r => r.json()),
216 {
217 onSuccess: data => console.log('Fetched', data.length, 'markets'),
218 onError: err => console.error('Failed:', err)
219 }
220)
221```
222
223### Debounce Hook
224
225```typescript
226export function useDebounce<T>(value: T, delay: number): T {
227 const [debouncedValue, setDebouncedValue] = useState<T>(value)
228
229 useEffect(() => {
230 const handler = setTimeout(() => {
231 setDebouncedValue(value)
232 }, delay)
233
234 return () => clearTimeout(handler)
235 }, [value, delay])
236
237 return debouncedValue
238}
239
240// Usage
241const [searchQuery, setSearchQuery] = useState('')
242const debouncedQuery = useDebounce(searchQuery, 500)
243
244useEffect(() => {
245 if (debouncedQuery) {
246 performSearch(debouncedQuery)
247 }
248}, [debouncedQuery])
249```
250
251## State Management Patterns
252
253### Context + Reducer Pattern
254
255```typescript
256interface State {
257 markets: Market[]
258 selectedMarket: Market | null
259 loading: boolean
260}
261
262type Action =
263 | { type: 'SET_MARKETS'; payload: Market[] }
264 | { type: 'SELECT_MARKET'; payload: Market }
265 | { type: 'SET_LOADING'; payload: boolean }
266
267function reducer(state: State, action: Action): State {
268 switch (action.type) {
269 case 'SET_MARKETS':
270 return { ...state, markets: action.payload }
271 case 'SELECT_MARKET':
272 return { ...state, selectedMarket: action.payload }
273 case 'SET_LOADING':
274 return { ...state, loading: action.payload }
275 default:
276 return state
277 }
278}
279
280const MarketContext = createContext<{
281 state: State
282 dispatch: Dispatch<Action>
283} | undefined>(undefined)
284
285export function MarketProvider({ children }: { children: React.ReactNode }) {
286 const [state, dispatch] = useReducer(reducer, {
287 markets: [],
288 selectedMarket: null,
289 loading: false
290 })
291
292 return (
293 <MarketContext.Provider value={{ state, dispatch }}>
294 {children}
295 </MarketContext.Provider>
296 )
297}
298
299export function useMarkets() {
300 const context = useContext(MarketContext)
301 if (!context) throw new Error('useMarkets must be used within MarketProvider')
302 return context
303}
304```
305
306## Performance Optimization
307
308### Memoization
309
310```typescript
311// PASS: useMemo for expensive computations
312// Copy before sorting - Array.prototype.sort mutates in place
313const sortedMarkets = useMemo(() => {
314 return [...markets].sort((a, b) => b.volume - a.volume)
315}, [markets])
316
317// PASS: useCallback for functions passed to children
318const handleSearch = useCallback((query: string) => {
319 setSearchQuery(query)
320}, [])
321
322// PASS: React.memo for pure components
323export const MarketCard = React.memo<MarketCardProps>(({ market }) => {
324 return (
325 <div className="market-card">
326 <h3>{market.name}</h3>
327 <p>{market.description}</p>
328 </div>
329 )
330})
331```
332
333### Code Splitting & Lazy Loading
334
335```typescript
336import { lazy, Suspense } from 'react'
337
338// PASS: Lazy load heavy components
339const HeavyChart = lazy(() => import('./HeavyChart'))
340const ThreeJsBackground = lazy(() => import('./ThreeJsBackground'))
341
342export function Dashboard() {
343 return (
344 <div>
345 <Suspense fallback={<ChartSkeleton />}>
346 <HeavyChart data={data} />
347 </Suspense>
348
349 <Suspense fallback={null}>
350 <ThreeJsBackground />
351 </Suspense>
352 </div>
353 )
354}
355```
356
357### Virtualization for Long Lists
358
359```typescript
360import { useVirtualizer } from '@tanstack/react-virtual'
361
362export function VirtualMarketList({ markets }: { markets: Market[] }) {
363 const parentRef = useRef<HTMLDivElement>(null)
364
365 const virtualizer = useVirtualizer({
366 count: markets.length,
367 getScrollElement: () => parentRef.current,
368 estimateSize: () => 100, // Estimated row height
369 overscan: 5 // Extra items to render
370 })
371
372 return (
373 <div ref={parentRef} style={{ height: '600px', overflow: 'auto' }}>
374 <div
375 style={{
376 height: ${virtualizer.getTotalSize()}px,
377 position: 'relative'
378 }}
379 >
380 {virtualizer.getVirtualItems().map(virtualRow => (
381 <div
382 key={virtualRow.index}
383 style={{
384 position: 'absolute',
385 top: 0,
386 left: 0,
387 width: '100%',
388 height: ${virtualRow.size}px,
389 transform: translateY(${virtualRow.start}px)
390 }}
391 >
392 <MarketCard market={markets[virtualRow.index]} />
393 </div>
394 ))}
395 </div>
396 </div>
397 )
398}
399```
400
401## Form Handling Patterns
402
403### Controlled Form with Validation
404
405```typescript
406interface FormData {
407 name: string
408 description: string
409 endDate: string
410}
411
412interface FormErrors {
413 name?: string
414 description?: string
415 endDate?: string
416}
417
418export function CreateMarketForm() {
419 const [formData, setFormData] = useState<FormData>({
420 name: '',
421 description: '',
422 endDate: ''
423 })
424
425 const [errors, setErrors] = useState<FormErrors>({})
426
427 const validate = (): boolean => {
428 const newErrors: FormErrors = {}
429
430 if (!formData.name.trim()) {
431 newErrors.name = 'Name is required'
432 } else if (formData.name.length > 200) {
433 newErrors.name = 'Name must be under 200 characters'
434 }
435
436 if (!formData.description.trim()) {
437 newErrors.description = 'Description is required'
438 }
439
440 if (!formData.endDate) {
441 newErrors.endDate = 'End date is required'
442 }
443
444 setErrors(newErrors)
445 return Object.keys(newErrors).length === 0
446 }
447
448 const handleSubmit = async (e: React.FormEvent) => {
449 e.preventDefault()
450
451 if (!validate()) return
452
453 try {
454 await createMarket(formData)
455 // Success handling
456 } catch (error) {
457 // Error handling
458 }
459 }
460
461 return (
462 <form onSubmit={handleSubmit}>
463 <input
464 value={formData.name}
465 onChange={e => setFormData(prev => ({ ...prev, name: e.target.value }))}
466 placeholder="Market name"
467 />
468 {errors.name && <span className="error">{errors.name}</span>}
469
470 {/* Other fields */}
471
472 <button type="submit">Create Market</button>
473 </form>
474 )
475}
476```
477
478## Error Boundary Pattern
479
480```typescript
481interface ErrorBoundaryState {
482 hasError: boolean
483 error: Error | null
484}
485
486export class ErrorBoundary extends React.Component<
487 { children: React.ReactNode },
488 ErrorBoundaryState
489> {
490 state: ErrorBoundaryState = {
491 hasError: false,
492 error: null
493 }
494
495 static getDerivedStateFromError(error: Error): ErrorBoundaryState {
496 return { hasError: true, error }
497 }
498
499 componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
500 console.error('Error boundary caught:', error, errorInfo)
501 }
502
503 render() {
504 if (this.state.hasError) {
505 return (
506 <div className="error-fallback">
507 <h2>Something went wrong</h2>
508 <p>{this.state.error?.message}</p>
509 <button onClick={() => this.setState({ hasError: false })}>
510 Try again
511 </button>
512 </div>
513 )
514 }
515
516 return this.props.children
517 }
518}
519
520// Usage
521<ErrorBoundary>
522 <App />
523</ErrorBoundary>
524```
525
526## Animation Patterns
527
528### Framer Motion Animations
529
530```typescript
531import { motion, AnimatePresence } from 'framer-motion'
532
533// PASS: List animations
534export function AnimatedMarketList({ markets }: { markets: Market[] }) {
535 return (
536 <AnimatePresence>
537 {markets.map(market => (
538 <motion.div
539 key={market.id}
540 initial={{ opacity: 0, y: 20 }}
541 animate={{ opacity: 1, y: 0 }}
542 exit={{ opacity: 0, y: -20 }}
543 transition={{ duration: 0.3 }}
544 >
545 <MarketCard market={market} />
546 </motion.div>
547 ))}
548 </AnimatePresence>
549 )
550}
551
552// PASS: Modal animations
553export function Modal({ isOpen, onClose, children }: ModalProps) {
554 return (
555 <AnimatePresence>
556 {isOpen && (
557 <>
558 <motion.div
559 className="modal-overlay"
560 initial={{ opacity: 0 }}
561 animate={{ opacity: 1 }}
562 exit={{ opacity: 0 }}
563 onClick={onClose}
564 />
565 <motion.div
566 className="modal-content"
567 initial={{ opacity: 0, scale: 0.9, y: 20 }}
568 animate={{ opacity: 1, scale: 1, y: 0 }}
569 exit={{ opacity: 0, scale: 0.9, y: 20 }}
570 >
571 {children}
572 </motion.div>
573 </>
574 )}
575 </AnimatePresence>
576 )
577}
578```
579
580## Accessibility Patterns
581
582### Keyboard Navigation
583
584```typescript
585export function Dropdown({ options, onSelect }: DropdownProps) {
586 const [isOpen, setIsOpen] = useState(false)
587 const [activeIndex, setActiveIndex] = useState(0)
588
589 const handleKeyDown = (e: React.KeyboardEvent) => {
590 switch (e.key) {
591 case 'ArrowDown':
592 e.preventDefault()
593 setActiveIndex(i => Math.min(i + 1, options.length - 1))
594 break
595 case 'ArrowUp':
596 e.preventDefault()
597 setActiveIndex(i => Math.max(i - 1, 0))
598 break
599 case 'Enter':
600 e.preventDefault()
601 onSelect(options[activeIndex])
602 setIsOpen(false)
603 break
604 case 'Escape':
605 setIsOpen(false)
606 break
607 }
608 }
609
610 return (
611 <div
612 role="combobox"
613 aria-expanded={isOpen}
614 aria-haspopup="listbox"
615 onKeyDown={handleKeyDown}
616 >
617 {/* Dropdown implementation */}
618 </div>
619 )
620}
621```
622
623### Focus Management
624
625```typescript
626export function Modal({ isOpen, onClose, children }: ModalProps) {
627 const modalRef = useRef<HTMLDivElement>(null)
628 const previousFocusRef = useRef<HTMLElement | null>(null)
629
630 useEffect(() => {
631 if (isOpen) {
632 // Save currently focused element
633 previousFocusRef.current = document.activeElement as HTMLElement
634
635 // Focus modal
636 modalRef.current?.focus()
637 } else {
638 // Restore focus when closing
639 previousFocusRef.current?.focus()
640 }
641 }, [isOpen])
642
643 return isOpen ? (
644 <div
645 ref={modalRef}
646 role="dialog"
647 aria-modal="true"
648 tabIndex={-1}
649 onKeyDown={e => e.key === 'Escape' && onClose()}
650 >
651 {children}
652 </div>
653 ) : null
654}
655```
656
657**Remember**: Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.
658
In the file
SKILL.md1,692 words
Files1
LicenceMIT
Why you can read it

Nothing in a skill executes. The client loads the text and the model follows it, so a skill can be audited the way a runbook is — by reading it.

What it costs in context

Skills are not billed by the call. They are paid for in context: every token the instructions occupy is a token your code, your diff and your conversation cannot use. Here is what this one takes and when it takes it.

≈70
always loaded
The name and description, so the model knows the skill exists and when to reach for it.
3,780
on trigger
The instruction body, read only when the skill fires.
1.9%
of a 200k window
Ten skills this size would take about 19% of the window before you open a file.
050k100k150k200k context window

3.9k tokens, estimated from the bundle at four bytes to the token, held for the rest of the session once it triggers. Middling. Fine to keep on in a project where you use it weekly, worth unloading in one where you never do.

Servers bill, skills cost

A server charges by the month. A skill charges once per session, in context, and then keeps charging it for as long as the session lives.

Before and after

The same question, put to the same model twice: once as it comes, and once with these instructions loaded.

No worked example has been published for this skill yet.

Adoption
Installsnone yet
Ratingno reviews yet

The procedure it runs

The procedure has not been published here. It is in the skill’s own SKILL.md, which its author has not sent to the marketplace yet.

Prose, not code

These steps are written for a model to follow, not executed by a runtime. It can still be told to skip one, and it will say so when it does.

Servers it uses

None. This skill calls no MCP servers at all.

Everything it needs is in the instructions, so it works in a project with nothing connected — the model reads the file and changes how it works with what it can already reach.

It writes no files and reaches no network. All it changes is how the model reasons and writes.

What it asks for
Writes filesno
Network accessno

Read from the allowed-tools line of this skill’s own SKILL.md. A skill grants no permissions of its own — it can only ask for tools your client already has.

What it will not do

Every skill is narrow, and the useful ones say where they stop. These are the jobs this one is the wrong tool for.

What this skill is not for has not been published here. Nothing is implied by that: it is a section the author has not filled in.

What is in the bundle

1 file, 15.4 kB on disk. A bundle is text throughout: the instructions the model reads, plus the templates it fills in.

  • SKILL.md15.4 kB
What is not in it

No dependencies and nothing executable: a skill is text the agent reads, so the bundle is 1 file you can review in full before installing. The MIT licence covers the templates and examples as well as the instructions.

Install

Installing copies the bundle into your project. Nothing runs at install time — the files sit on disk until the model reads them.

$12 once
Frontend Development Patterns · MIT · affaan-m
one-time
Price$12 once
LicenceMIT — the author’s, unchanged by this purchase
Paid throughStripe, once, on the card you add at the checkout
Keeps workingfor good — the files are yours once they are on disk
Updatesevery update its author ships, delivered through this account

You can read the whole bundle before paying — the SKILL.md above is the product, not a preview of it. What the money buys is the delivery: the folder packaged and handed to your machine by key, every update its author ships, and our support if it does not do what this listing says. The terms of use are MIT, set by the author and unchanged by buying it here.

Payment runs through Stripe, on a page like this one rather than a redirect. Once there is an account it joins the same mcprush invoice as everything else you run, so there is never a second card to enter.

Which clients pick it up on their own

A skill is a folder of text. A client with a skills folder reads it without being told; everywhere else the same text works, it is just handed to the model rather than found.

Claude Code.claude/skills/
Claude Desktop
ChatGPT
Cursor.cursor/skills/
VS Code.github/skills/
Codex CLI.agents/skills/
Gemini CLI.gemini/skills/
Grok.grok/skills/
Zed.agents/skills/
Windsurf.windsurf/skills/
Agent SDK.claude/skills/
HTTP / API
This release
Versionnot versioned
Publishedno release date on file
Price$12
Referenceaffaan-m/frontend-development-patterns

Versions

Its author publishes no version number, so there is nothing here to pin to: what you install is the folder as it stands today. Instructions change more often than APIs do — a skill can be rewritten entirely without anything it depends on moving.

v
  • No earlier releases have been published to the marketplace.
Pinning

Nothing to pin to: this skill carries no version number of its own. What you install is what the folder holds on the day you install it.

Reviews

no reviews yet · no installs yet

Nobody has reviewed this skill yet. The rating is the mean of the reviews written here, so there is none until somebody writes the first.

Who can post

Only accounts that have had the skill installed for fourteen days, so a review is written after living with it rather than after reading it. Publishers may reply once.

Publisher
Servers0