frontend-patterns

安装量: 2.3K
排名: #822

安装

npx skills add https://github.com/affaan-m/everything-claude-code --skill frontend-patterns
Frontend Development Patterns
Modern frontend patterns for React, Next.js, and performant user interfaces.
When to Activate
Building React components (composition, props, rendering)
Managing state (useState, useReducer, Zustand, Context)
Implementing data fetching (SWR, React Query, server components)
Optimizing performance (memoization, virtualization, code splitting)
Working with forms (validation, controlled inputs, Zod schemas)
Handling client-side routing and navigation
Building accessible, responsive UI patterns
Component Patterns
Composition Over Inheritance
// ✅ GOOD: Component composition
interface
CardProps
{
children
:
React
.
ReactNode
variant
?
:
'default'
|
'outlined'
}
export
function
Card
(
{
children
,
variant
=
'default'
}
:
CardProps
)
{
return
<
div className
=
{
`
card card-
${
variant
}
`
}
>
{
children
}
<
/
div
>
}
export
function
CardHeader
(
{
children
}
:
{
children
:
React
.
ReactNode
}
)
{
return
<
div className
=
"card-header"
>
{
children
}
<
/
div
>
}
export
function
CardBody
(
{
children
}
:
{
children
:
React
.
ReactNode
}
)
{
return
<
div className
=
"card-body"
>
{
children
}
<
/
div
>
}
// Usage
<
Card
>
<
CardHeader
>
Title
<
/
CardHeader
>
<
CardBody
>
Content
<
/
CardBody
>
<
/
Card
>
Compound Components
interface
TabsContextValue
{
activeTab
:
string
setActiveTab
:
(
tab
:
string
)
=>
void
}
const
TabsContext
=
createContext
<
TabsContextValue
|
undefined
>
(
undefined
)
export
function
Tabs
(
{
children
,
defaultTab
}
:
{
children
:
React
.
ReactNode
defaultTab
:
string
}
)
{
const
[
activeTab
,
setActiveTab
]
=
useState
(
defaultTab
)
return
(
<
TabsContext
.
Provider value
=
{
{
activeTab
,
setActiveTab
}
}
>
{
children
}
<
/
TabsContext
.
Provider
>
)
}
export
function
TabList
(
{
children
}
:
{
children
:
React
.
ReactNode
}
)
{
return
<
div className
=
"tab-list"
>
{
children
}
<
/
div
>
}
export
function
Tab
(
{
id
,
children
}
:
{
id
:
string
,
children
:
React
.
ReactNode
}
)
{
const
context
=
useContext
(
TabsContext
)
if
(
!
context
)
throw
new
Error
(
'Tab must be used within Tabs'
)
return
(
<
button
className
=
{
context
.
activeTab
===
id
?
'active'
:
''
}
onClick
=
{
(
)
=>
context
.
setActiveTab
(
id
)
}
>
{
children
}
<
/
button
>
)
}
// Usage
<
Tabs defaultTab
=
"overview"
>
<
TabList
>
<
Tab id
=
"overview"
>
Overview
<
/
Tab
>
<
Tab id
=
"details"
>
Details
<
/
Tab
>
<
/
TabList
>
<
/
Tabs
>
Render Props Pattern
interface
DataLoaderProps
<
T
>
{
url
:
string
children
:
(
data
:
T
|
null
,
loading
:
boolean
,
error
:
Error
|
null
)
=>
React
.
ReactNode
}
export
function
DataLoader
<
T
>
(
{
url
,
children
}
:
DataLoaderProps
<
T
>
)
{
const
[
data
,
setData
]
=
useState
<
T
|
null
>
(
null
)
const
[
loading
,
setLoading
]
=
useState
(
true
)
const
[
error
,
setError
]
=
useState
<
Error
|
null
>
(
null
)
useEffect
(
(
)
=>
{
fetch
(
url
)
.
then
(
res
=>
res
.
json
(
)
)
.
then
(
setData
)
.
catch
(
setError
)
.
finally
(
(
)
=>
setLoading
(
false
)
)
}
,
[
url
]
)
return
<
>
{
children
(
data
,
loading
,
error
)
}
<
/
>
}
// Usage
<
DataLoader
<
Market
[
]
>
url
=
"/api/markets"
>
{
(
markets
,
loading
,
error
)
=>
{
if
(
loading
)
return
<
Spinner
/
>
if
(
error
)
return
<
Error error
=
{
error
}
/
>
return
<
MarketList markets
=
{
markets
!
}
/
>
}
}
<
/
DataLoader
>
Custom Hooks Patterns
State Management Hook
export
function
useToggle
(
initialValue
=
false
)
:
[
boolean
,
(
)
=>
void
]
{
const
[
value
,
setValue
]
=
useState
(
initialValue
)
const
toggle
=
useCallback
(
(
)
=>
{
setValue
(
v
=>
!
v
)
}
,
[
]
)
return
[
value
,
toggle
]
}
// Usage
const
[
isOpen
,
toggleOpen
]
=
useToggle
(
)
Async Data Fetching Hook
interface
UseQueryOptions
<
T
>
{
onSuccess
?
:
(
data
:
T
)
=>
void
onError
?
:
(
error
:
Error
)
=>
void
enabled
?
:
boolean
}
export
function
useQuery
<
T
>
(
key
:
string
,
fetcher
:
(
)
=>
Promise
<
T
>
,
options
?
:
UseQueryOptions
<
T
>
)
{
const
[
data
,
setData
]
=
useState
<
T
|
null
>
(
null
)
const
[
error
,
setError
]
=
useState
<
Error
|
null
>
(
null
)
const
[
loading
,
setLoading
]
=
useState
(
false
)
const
refetch
=
useCallback
(
async
(
)
=>
{
setLoading
(
true
)
setError
(
null
)
try
{
const
result
=
await
fetcher
(
)
setData
(
result
)
options
?.
onSuccess
?.
(
result
)
}
catch
(
err
)
{
const
error
=
err
as
Error
setError
(
error
)
options
?.
onError
?.
(
error
)
}
finally
{
setLoading
(
false
)
}
}
,
[
fetcher
,
options
]
)
useEffect
(
(
)
=>
{
if
(
options
?.
enabled
!==
false
)
{
refetch
(
)
}
}
,
[
key
,
refetch
,
options
?.
enabled
]
)
return
{
data
,
error
,
loading
,
refetch
}
}
// Usage
const
{
data
:
markets
,
loading
,
error
,
refetch
}
=
useQuery
(
'markets'
,
(
)
=>
fetch
(
'/api/markets'
)
.
then
(
r
=>
r
.
json
(
)
)
,
{
onSuccess
:
data
=>
console
.
log
(
'Fetched'
,
data
.
length
,
'markets'
)
,
onError
:
err
=>
console
.
error
(
'Failed:'
,
err
)
}
)
Debounce Hook
export
function
useDebounce
<
T
>
(
value
:
T
,
delay
:
number
)
:
T
{
const
[
debouncedValue
,
setDebouncedValue
]
=
useState
<
T
>
(
value
)
useEffect
(
(
)
=>
{
const
handler
=
setTimeout
(
(
)
=>
{
setDebouncedValue
(
value
)
}
,
delay
)
return
(
)
=>
clearTimeout
(
handler
)
}
,
[
value
,
delay
]
)
return
debouncedValue
}
// Usage
const
[
searchQuery
,
setSearchQuery
]
=
useState
(
''
)
const
debouncedQuery
=
useDebounce
(
searchQuery
,
500
)
useEffect
(
(
)
=>
{
if
(
debouncedQuery
)
{
performSearch
(
debouncedQuery
)
}
}
,
[
debouncedQuery
]
)
State Management Patterns
Context + Reducer Pattern
interface
State
{
markets
:
Market
[
]
selectedMarket
:
Market
|
null
loading
:
boolean
}
type
Action
=
|
{
type
:
'SET_MARKETS'
;
payload
:
Market
[
]
}
|
{
type
:
'SELECT_MARKET'
;
payload
:
Market
}
|
{
type
:
'SET_LOADING'
;
payload
:
boolean
}
function
reducer
(
state
:
State
,
action
:
Action
)
:
State
{
switch
(
action
.
type
)
{
case
'SET_MARKETS'
:
return
{
...
state
,
markets
:
action
.
payload
}
case
'SELECT_MARKET'
:
return
{
...
state
,
selectedMarket
:
action
.
payload
}
case
'SET_LOADING'
:
return
{
...
state
,
loading
:
action
.
payload
}
default
:
return
state
}
}
const
MarketContext
=
createContext
<
{
state
:
State
dispatch
:
Dispatch
<
Action
>
}
|
undefined
>
(
undefined
)
export
function
MarketProvider
(
{
children
}
:
{
children
:
React
.
ReactNode
}
)
{
const
[
state
,
dispatch
]
=
useReducer
(
reducer
,
{
markets
:
[
]
,
selectedMarket
:
null
,
loading
:
false
}
)
return
(
<
MarketContext
.
Provider value
=
{
{
state
,
dispatch
}
}
>
{
children
}
<
/
MarketContext
.
Provider
>
)
}
export
function
useMarkets
(
)
{
const
context
=
useContext
(
MarketContext
)
if
(
!
context
)
throw
new
Error
(
'useMarkets must be used within MarketProvider'
)
return
context
}
Performance Optimization
Memoization
// ✅ useMemo for expensive computations
const
sortedMarkets
=
useMemo
(
(
)
=>
{
return
markets
.
sort
(
(
a
,
b
)
=>
b
.
volume
-
a
.
volume
)
}
,
[
markets
]
)
// ✅ useCallback for functions passed to children
const
handleSearch
=
useCallback
(
(
query
:
string
)
=>
{
setSearchQuery
(
query
)
}
,
[
]
)
// ✅ React.memo for pure components
export
const
MarketCard
=
React
.
memo
<
MarketCardProps
>
(
(
{
market
}
)
=>
{
return
(
<
div className
=
"market-card"
>
<
h3
>
{
market
.
name
}
<
/
h3
>
<
p
>
{
market
.
description
}
<
/
p
>
<
/
div
>
)
}
)
Code Splitting & Lazy Loading
import
{
lazy
,
Suspense
}
from
'react'
// ✅ Lazy load heavy components
const
HeavyChart
=
lazy
(
(
)
=>
import
(
'./HeavyChart'
)
)
const
ThreeJsBackground
=
lazy
(
(
)
=>
import
(
'./ThreeJsBackground'
)
)
export
function
Dashboard
(
)
{
return
(
<
div
>
<
Suspense fallback
=
{
<
ChartSkeleton
/
>
}
>
<
HeavyChart data
=
{
data
}
/
>
<
/
Suspense
>
<
Suspense fallback
=
{
null
}
>
<
ThreeJsBackground
/
>
<
/
Suspense
>
<
/
div
>
)
}
Virtualization for Long Lists
import
{
useVirtualizer
}
from
'@tanstack/react-virtual'
export
function
VirtualMarketList
(
{
markets
}
:
{
markets
:
Market
[
]
}
)
{
const
parentRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
virtualizer
=
useVirtualizer
(
{
count
:
markets
.
length
,
getScrollElement
:
(
)
=>
parentRef
.
current
,
estimateSize
:
(
)
=>
100
,
// Estimated row height
overscan
:
5
// Extra items to render
}
)
return
(
<
div ref
=
{
parentRef
}
style
=
{
{
height
:
'600px'
,
overflow
:
'auto'
}
}
>
<
div
style
=
{
{
height
:
`
${
virtualizer
.
getTotalSize
(
)
}
px
`
,
position
:
'relative'
}
}
>
{
virtualizer
.
getVirtualItems
(
)
.
map
(
virtualRow
=>
(
<
div
key
=
{
virtualRow
.
index
}
style
=
{
{
position
:
'absolute'
,
top
:
0
,
left
:
0
,
width
:
'100%'
,
height
:
`
${
virtualRow
.
size
}
px
`
,
transform
:
`
translateY(
${
virtualRow
.
start
}
px)
`
}
}
>
<
MarketCard market
=
{
markets
[
virtualRow
.
index
]
}
/
>
<
/
div
>
)
)
}
<
/
div
>
<
/
div
>
)
}
Form Handling Patterns
Controlled Form with Validation
interface
FormData
{
name
:
string
description
:
string
endDate
:
string
}
interface
FormErrors
{
name
?
:
string
description
?
:
string
endDate
?
:
string
}
export
function
CreateMarketForm
(
)
{
const
[
formData
,
setFormData
]
=
useState
<
FormData
>
(
{
name
:
''
,
description
:
''
,
endDate
:
''
}
)
const
[
errors
,
setErrors
]
=
useState
<
FormErrors
>
(
{
}
)
const
validate
=
(
)
:
boolean
=>
{
const
newErrors
:
FormErrors
=
{
}
if
(
!
formData
.
name
.
trim
(
)
)
{
newErrors
.
name
=
'Name is required'
}
else
if
(
formData
.
name
.
length
>
200
)
{
newErrors
.
name
=
'Name must be under 200 characters'
}
if
(
!
formData
.
description
.
trim
(
)
)
{
newErrors
.
description
=
'Description is required'
}
if
(
!
formData
.
endDate
)
{
newErrors
.
endDate
=
'End date is required'
}
setErrors
(
newErrors
)
return
Object
.
keys
(
newErrors
)
.
length
===
0
}
const
handleSubmit
=
async
(
e
:
React
.
FormEvent
)
=>
{
e
.
preventDefault
(
)
if
(
!
validate
(
)
)
return
try
{
await
createMarket
(
formData
)
// Success handling
}
catch
(
error
)
{
// Error handling
}
}
return
(
<
form onSubmit
=
{
handleSubmit
}
>
<
input
value
=
{
formData
.
name
}
onChange
=
{
e
=>
setFormData
(
prev
=>
(
{
...
prev
,
name
:
e
.
target
.
value
}
)
)
}
placeholder
=
"Market name"
/
>
{
errors
.
name
&&
<
span className
=
"error"
>
{
errors
.
name
}
<
/
span
>
}
{
/ Other fields /
}
<
button type
=
"submit"
>
Create Market
<
/
button
>
<
/
form
>
)
}
Error Boundary Pattern
interface
ErrorBoundaryState
{
hasError
:
boolean
error
:
Error
|
null
}
export
class
ErrorBoundary
extends
React
.
Component
<
{
children
:
React
.
ReactNode
}
,
ErrorBoundaryState
>
{
state
:
ErrorBoundaryState
=
{
hasError
:
false
,
error
:
null
}
static
getDerivedStateFromError
(
error
:
Error
)
:
ErrorBoundaryState
{
return
{
hasError
:
true
,
error
}
}
componentDidCatch
(
error
:
Error
,
errorInfo
:
React
.
ErrorInfo
)
{
console
.
error
(
'Error boundary caught:'
,
error
,
errorInfo
)
}
render
(
)
{
if
(
this
.
state
.
hasError
)
{
return
(
<
div className
=
"error-fallback"
>
<
h2
>
Something went wrong
<
/
h2
>
<
p
>
{
this
.
state
.
error
?.
message
}
<
/
p
>
<
button onClick
=
{
(
)
=>
this
.
setState
(
{
hasError
:
false
}
)
}
>
Try again
<
/
button
>
<
/
div
>
)
}
return
this
.
props
.
children
}
}
// Usage
<
ErrorBoundary
>
<
App
/
>
<
/
ErrorBoundary
>
Animation Patterns
Framer Motion Animations
import
{
motion
,
AnimatePresence
}
from
'framer-motion'
// ✅ List animations
export
function
AnimatedMarketList
(
{
markets
}
:
{
markets
:
Market
[
]
}
)
{
return
(
<
AnimatePresence
>
{
markets
.
map
(
market
=>
(
<
motion
.
div
key
=
{
market
.
id
}
initial
=
{
{
opacity
:
0
,
y
:
20
}
}
animate
=
{
{
opacity
:
1
,
y
:
0
}
}
exit
=
{
{
opacity
:
0
,
y
:
-
20
}
}
transition
=
{
{
duration
:
0.3
}
}
>
<
MarketCard market
=
{
market
}
/
>
<
/
motion
.
div
>
)
)
}
<
/
AnimatePresence
>
)
}
// ✅ Modal animations
export
function
Modal
(
{
isOpen
,
onClose
,
children
}
:
ModalProps
)
{
return
(
<
AnimatePresence
>
{
isOpen
&&
(
<
>
<
motion
.
div
className
=
"modal-overlay"
initial
=
{
{
opacity
:
0
}
}
animate
=
{
{
opacity
:
1
}
}
exit
=
{
{
opacity
:
0
}
}
onClick
=
{
onClose
}
/
>
<
motion
.
div
className
=
"modal-content"
initial
=
{
{
opacity
:
0
,
scale
:
0.9
,
y
:
20
}
}
animate
=
{
{
opacity
:
1
,
scale
:
1
,
y
:
0
}
}
exit
=
{
{
opacity
:
0
,
scale
:
0.9
,
y
:
20
}
}
>
{
children
}
<
/
motion
.
div
>
<
/
>
)
}
<
/
AnimatePresence
>
)
}
Accessibility Patterns
Keyboard Navigation
export
function
Dropdown
(
{
options
,
onSelect
}
:
DropdownProps
)
{
const
[
isOpen
,
setIsOpen
]
=
useState
(
false
)
const
[
activeIndex
,
setActiveIndex
]
=
useState
(
0
)
const
handleKeyDown
=
(
e
:
React
.
KeyboardEvent
)
=>
{
switch
(
e
.
key
)
{
case
'ArrowDown'
:
e
.
preventDefault
(
)
setActiveIndex
(
i
=>
Math
.
min
(
i
+
1
,
options
.
length
-
1
)
)
break
case
'ArrowUp'
:
e
.
preventDefault
(
)
setActiveIndex
(
i
=>
Math
.
max
(
i
-
1
,
0
)
)
break
case
'Enter'
:
e
.
preventDefault
(
)
onSelect
(
options
[
activeIndex
]
)
setIsOpen
(
false
)
break
case
'Escape'
:
setIsOpen
(
false
)
break
}
}
return
(
<
div
role
=
"combobox"
aria
-
expanded
=
{
isOpen
}
aria
-
haspopup
=
"listbox"
onKeyDown
=
{
handleKeyDown
}
>
{
/ Dropdown implementation /
}
<
/
div
>
)
}
Focus Management
export
function
Modal
(
{
isOpen
,
onClose
,
children
}
:
ModalProps
)
{
const
modalRef
=
useRef
<
HTMLDivElement
>
(
null
)
const
previousFocusRef
=
useRef
<
HTMLElement
|
null
>
(
null
)
useEffect
(
(
)
=>
{
if
(
isOpen
)
{
// Save currently focused element
previousFocusRef
.
current
=
document
.
activeElement
as
HTMLElement
// Focus modal
modalRef
.
current
?.
focus
(
)
}
else
{
// Restore focus when closing
previousFocusRef
.
current
?.
focus
(
)
}
}
,
[
isOpen
]
)
return
isOpen
?
(
<
div
ref
=
{
modalRef
}
role
=
"dialog"
aria
-
modal
=
"true"
tabIndex
=
{
-
1
}
onKeyDown
=
{
e
=>
e
.
key
===
'Escape'
&&
onClose
(
)
}
>
{
children
}
<
/
div
>
)
:
null
}
Remember
Modern frontend patterns enable maintainable, performant user interfaces. Choose patterns that fit your project complexity.
返回排行榜