Add NDK skill documentation and examples
- Introduced comprehensive documentation for the Nostr Development Kit (NDK) including an overview, quick reference, and troubleshooting guide. - Added detailed examples covering initialization, authentication, event publishing, querying, and user profile management. - Structured the documentation to facilitate quick lookups and deep learning, based on real-world usage patterns from the Plebeian Market application. - Created an index for examples to enhance usability and navigation. - Bumped version to 1.0.0 to reflect the addition of this new skill set.
This commit is contained in:
291
.claude/skills/react/references/hooks-quick-reference.md
Normal file
291
.claude/skills/react/references/hooks-quick-reference.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# React Hooks Quick Reference
|
||||
|
||||
## State Hooks
|
||||
|
||||
### useState
|
||||
```typescript
|
||||
const [state, setState] = useState<Type>(initialValue)
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Functional update
|
||||
setCount(prev => prev + 1)
|
||||
|
||||
// Lazy initialization
|
||||
const [state, setState] = useState(() => expensiveComputation())
|
||||
```
|
||||
|
||||
### useReducer
|
||||
```typescript
|
||||
type State = { count: number }
|
||||
type Action = { type: 'increment' } | { type: 'decrement' }
|
||||
|
||||
const reducer = (state: State, action: Action): State => {
|
||||
switch (action.type) {
|
||||
case 'increment': return { count: state.count + 1 }
|
||||
case 'decrement': return { count: state.count - 1 }
|
||||
}
|
||||
}
|
||||
|
||||
const [state, dispatch] = useReducer(reducer, { count: 0 })
|
||||
dispatch({ type: 'increment' })
|
||||
```
|
||||
|
||||
### useActionState (React 19)
|
||||
```typescript
|
||||
const [state, formAction, isPending] = useActionState(
|
||||
async (previousState, formData: FormData) => {
|
||||
// Server action
|
||||
return await processForm(formData)
|
||||
},
|
||||
initialState
|
||||
)
|
||||
|
||||
<form action={formAction}>
|
||||
<button disabled={isPending}>Submit</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Effect Hooks
|
||||
|
||||
### useEffect
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
// Side effect
|
||||
const subscription = api.subscribe()
|
||||
|
||||
// Cleanup
|
||||
return () => subscription.unsubscribe()
|
||||
}, [dependencies])
|
||||
```
|
||||
|
||||
**Timing**: After render & paint
|
||||
**Use for**: Data fetching, subscriptions, DOM mutations
|
||||
|
||||
### useLayoutEffect
|
||||
```typescript
|
||||
useLayoutEffect(() => {
|
||||
// Runs before paint
|
||||
const height = ref.current.offsetHeight
|
||||
setHeight(height)
|
||||
}, [])
|
||||
```
|
||||
|
||||
**Timing**: After render, before paint
|
||||
**Use for**: DOM measurements, preventing flicker
|
||||
|
||||
### useInsertionEffect
|
||||
```typescript
|
||||
useInsertionEffect(() => {
|
||||
// Insert styles before any DOM reads
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
document.head.appendChild(style)
|
||||
return () => document.head.removeChild(style)
|
||||
}, [css])
|
||||
```
|
||||
|
||||
**Timing**: Before any DOM mutations
|
||||
**Use for**: CSS-in-JS libraries
|
||||
|
||||
## Performance Hooks
|
||||
|
||||
### useMemo
|
||||
```typescript
|
||||
const memoizedValue = useMemo(() => {
|
||||
return expensiveComputation(a, b)
|
||||
}, [a, b])
|
||||
```
|
||||
|
||||
**Use for**: Expensive calculations, stable object references
|
||||
|
||||
### useCallback
|
||||
```typescript
|
||||
const memoizedCallback = useCallback(() => {
|
||||
doSomething(a, b)
|
||||
}, [a, b])
|
||||
```
|
||||
|
||||
**Use for**: Passing callbacks to optimized components
|
||||
|
||||
## Ref Hooks
|
||||
|
||||
### useRef
|
||||
```typescript
|
||||
// DOM reference
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
ref.current?.focus()
|
||||
|
||||
// Mutable value (doesn't trigger re-render)
|
||||
const countRef = useRef(0)
|
||||
countRef.current += 1
|
||||
```
|
||||
|
||||
### useImperativeHandle
|
||||
```typescript
|
||||
useImperativeHandle(ref, () => ({
|
||||
focus: () => inputRef.current?.focus(),
|
||||
clear: () => inputRef.current && (inputRef.current.value = '')
|
||||
}), [])
|
||||
```
|
||||
|
||||
## Context Hook
|
||||
|
||||
### useContext
|
||||
```typescript
|
||||
const value = useContext(MyContext)
|
||||
```
|
||||
|
||||
Must be used within a Provider.
|
||||
|
||||
## Transition Hooks
|
||||
|
||||
### useTransition
|
||||
```typescript
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
startTransition(() => {
|
||||
setState(newValue) // Non-urgent update
|
||||
})
|
||||
```
|
||||
|
||||
### useDeferredValue
|
||||
```typescript
|
||||
const [input, setInput] = useState('')
|
||||
const deferredInput = useDeferredValue(input)
|
||||
|
||||
// Use deferredInput for expensive operations
|
||||
const results = useMemo(() => search(deferredInput), [deferredInput])
|
||||
```
|
||||
|
||||
## Optimistic Updates (React 19)
|
||||
|
||||
### useOptimistic
|
||||
```typescript
|
||||
const [optimisticState, addOptimistic] = useOptimistic(
|
||||
actualState,
|
||||
(currentState, optimisticValue) => {
|
||||
return [...currentState, optimisticValue]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Other Hooks
|
||||
|
||||
### useId
|
||||
```typescript
|
||||
const id = useId()
|
||||
<label htmlFor={id}>Name</label>
|
||||
<input id={id} />
|
||||
```
|
||||
|
||||
### useSyncExternalStore
|
||||
```typescript
|
||||
const state = useSyncExternalStore(
|
||||
subscribe,
|
||||
getSnapshot,
|
||||
getServerSnapshot
|
||||
)
|
||||
```
|
||||
|
||||
### useDebugValue
|
||||
```typescript
|
||||
useDebugValue(isOnline ? 'Online' : 'Offline')
|
||||
```
|
||||
|
||||
### use (React 19)
|
||||
```typescript
|
||||
// Read context or promise
|
||||
const value = use(MyContext)
|
||||
const data = use(fetchPromise) // Must be in Suspense
|
||||
```
|
||||
|
||||
## Form Hooks (React DOM)
|
||||
|
||||
### useFormStatus
|
||||
```typescript
|
||||
import { useFormStatus } from 'react-dom'
|
||||
|
||||
const { pending, data, method, action } = useFormStatus()
|
||||
```
|
||||
|
||||
## Hook Rules
|
||||
|
||||
1. **Only call at top level** - Not in loops, conditions, or nested functions
|
||||
2. **Only call from React functions** - Components or custom hooks
|
||||
3. **Custom hooks start with "use"** - Naming convention
|
||||
4. **Same hooks in same order** - Every render must call same hooks
|
||||
|
||||
## Dependencies Best Practices
|
||||
|
||||
1. **Include all used values** - Variables, props, state from component scope
|
||||
2. **Use ESLint plugin** - `eslint-plugin-react-hooks` enforces rules
|
||||
3. **Functions as dependencies** - Wrap with useCallback or define outside component
|
||||
4. **Object/array dependencies** - Use useMemo for stable references
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Fetching Data
|
||||
```typescript
|
||||
const [data, setData] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController()
|
||||
|
||||
fetch('/api/data', { signal: controller.signal })
|
||||
.then(res => res.json())
|
||||
.then(setData)
|
||||
.catch(setError)
|
||||
.finally(() => setLoading(false))
|
||||
|
||||
return () => controller.abort()
|
||||
}, [])
|
||||
```
|
||||
|
||||
### Debouncing
|
||||
```typescript
|
||||
const [value, setValue] = useState('')
|
||||
const [debouncedValue, setDebouncedValue] = useState(value)
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
setDebouncedValue(value)
|
||||
}, 500)
|
||||
|
||||
return () => clearTimeout(timer)
|
||||
}, [value])
|
||||
```
|
||||
|
||||
### Previous Value
|
||||
```typescript
|
||||
const usePrevious = <T,>(value: T): T | undefined => {
|
||||
const ref = useRef<T>()
|
||||
useEffect(() => {
|
||||
ref.current = value
|
||||
})
|
||||
return ref.current
|
||||
}
|
||||
```
|
||||
|
||||
### Interval
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => {
|
||||
setCount(c => c + 1)
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(id)
|
||||
}, [])
|
||||
```
|
||||
|
||||
### Event Listeners
|
||||
```typescript
|
||||
useEffect(() => {
|
||||
const handleResize = () => setWidth(window.innerWidth)
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
return () => window.removeEventListener('resize', handleResize)
|
||||
}, [])
|
||||
```
|
||||
|
||||
658
.claude/skills/react/references/performance.md
Normal file
658
.claude/skills/react/references/performance.md
Normal file
@@ -0,0 +1,658 @@
|
||||
# React Performance Optimization Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide covers performance optimization strategies for React 19 applications.
|
||||
|
||||
## Measurement & Profiling
|
||||
|
||||
### React DevTools Profiler
|
||||
|
||||
Record performance data:
|
||||
1. Open React DevTools
|
||||
2. Go to Profiler tab
|
||||
3. Click record button
|
||||
4. Interact with app
|
||||
5. Stop recording
|
||||
6. Analyze flame graph and ranked chart
|
||||
|
||||
### Profiler Component
|
||||
|
||||
```typescript
|
||||
import { Profiler } from 'react'
|
||||
|
||||
const App = () => {
|
||||
const onRender = (
|
||||
id: string,
|
||||
phase: 'mount' | 'update',
|
||||
actualDuration: number,
|
||||
baseDuration: number,
|
||||
startTime: number,
|
||||
commitTime: number
|
||||
) => {
|
||||
console.log({
|
||||
component: id,
|
||||
phase,
|
||||
actualDuration, // Time spent rendering this update
|
||||
baseDuration // Estimated time without memoization
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Profiler id="App" onRender={onRender}>
|
||||
<YourApp />
|
||||
</Profiler>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Performance Metrics
|
||||
|
||||
```typescript
|
||||
// Custom performance tracking
|
||||
const startTime = performance.now()
|
||||
// ... do work
|
||||
const endTime = performance.now()
|
||||
console.log(`Operation took ${endTime - startTime}ms`)
|
||||
|
||||
// React rendering metrics
|
||||
import { unstable_trace as trace } from 'react'
|
||||
|
||||
trace('expensive-operation', async () => {
|
||||
await performExpensiveOperation()
|
||||
})
|
||||
```
|
||||
|
||||
## Memoization Strategies
|
||||
|
||||
### React.memo
|
||||
|
||||
Prevent unnecessary re-renders:
|
||||
|
||||
```typescript
|
||||
// Basic memoization
|
||||
const ExpensiveComponent = memo(({ data }: Props) => {
|
||||
return <div>{processData(data)}</div>
|
||||
})
|
||||
|
||||
// Custom comparison
|
||||
const MemoizedComponent = memo(
|
||||
({ user }: Props) => <UserCard user={user} />,
|
||||
(prevProps, nextProps) => {
|
||||
// Return true if props are equal (skip render)
|
||||
return prevProps.user.id === nextProps.user.id
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Component renders often with same props
|
||||
- Rendering is expensive
|
||||
- Component receives complex prop objects
|
||||
|
||||
**When NOT to use:**
|
||||
- Props change frequently
|
||||
- Component is already fast
|
||||
- Premature optimization
|
||||
|
||||
### useMemo
|
||||
|
||||
Memoize computed values:
|
||||
|
||||
```typescript
|
||||
const SortedList = ({ items, filter }: Props) => {
|
||||
// Without memoization - runs every render
|
||||
const filteredItems = items.filter(item => item.type === filter)
|
||||
const sortedItems = filteredItems.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
// With memoization - only runs when dependencies change
|
||||
const sortedFilteredItems = useMemo(() => {
|
||||
const filtered = items.filter(item => item.type === filter)
|
||||
return filtered.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}, [items, filter])
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{sortedFilteredItems.map(item => (
|
||||
<li key={item.id}>{item.name}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Expensive calculations (sorting, filtering large arrays)
|
||||
- Creating stable object references
|
||||
- Computed values used as dependencies
|
||||
|
||||
### useCallback
|
||||
|
||||
Memoize callback functions:
|
||||
|
||||
```typescript
|
||||
const Parent = () => {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
// Without useCallback - new function every render
|
||||
const handleClick = () => {
|
||||
setCount(c => c + 1)
|
||||
}
|
||||
|
||||
// With useCallback - stable function reference
|
||||
const handleClickMemo = useCallback(() => {
|
||||
setCount(c => c + 1)
|
||||
}, [])
|
||||
|
||||
return <MemoizedChild onClick={handleClickMemo} />
|
||||
}
|
||||
|
||||
const MemoizedChild = memo(({ onClick }: Props) => {
|
||||
return <button onClick={onClick}>Click</button>
|
||||
})
|
||||
```
|
||||
|
||||
**When to use:**
|
||||
- Passing callbacks to memoized components
|
||||
- Callback is used in dependency array
|
||||
- Callback is expensive to create
|
||||
|
||||
## React Compiler (Automatic Optimization)
|
||||
|
||||
### Enable React Compiler
|
||||
|
||||
React 19 can automatically optimize without manual memoization:
|
||||
|
||||
```javascript
|
||||
// babel.config.js
|
||||
module.exports = {
|
||||
plugins: [
|
||||
['react-compiler', {
|
||||
compilationMode: 'all', // Optimize all components
|
||||
}]
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Compilation Modes
|
||||
|
||||
```javascript
|
||||
{
|
||||
compilationMode: 'annotation', // Only components with "use memo"
|
||||
compilationMode: 'all', // All components (recommended)
|
||||
compilationMode: 'infer' // Based on component complexity
|
||||
}
|
||||
```
|
||||
|
||||
### Directives
|
||||
|
||||
```typescript
|
||||
// Force memoization
|
||||
'use memo'
|
||||
const Component = ({ data }: Props) => {
|
||||
return <div>{data}</div>
|
||||
}
|
||||
|
||||
// Prevent memoization
|
||||
'use no memo'
|
||||
const SimpleComponent = ({ text }: Props) => {
|
||||
return <span>{text}</span>
|
||||
}
|
||||
```
|
||||
|
||||
## State Management Optimization
|
||||
|
||||
### State Colocation
|
||||
|
||||
Keep state as close as possible to where it's used:
|
||||
|
||||
```typescript
|
||||
// Bad - state too high
|
||||
const App = () => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Content />
|
||||
<Modal show={showModal} onClose={() => setShowModal(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// Good - state colocated
|
||||
const App = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Content />
|
||||
<ModalContainer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const ModalContainer = () => {
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
|
||||
return <Modal show={showModal} onClose={() => setShowModal(false)} />
|
||||
}
|
||||
```
|
||||
|
||||
### Split Context
|
||||
|
||||
Avoid unnecessary re-renders by splitting context:
|
||||
|
||||
```typescript
|
||||
// Bad - single context causes all consumers to re-render
|
||||
const AppContext = createContext({ user, theme, settings })
|
||||
|
||||
// Good - split into separate contexts
|
||||
const UserContext = createContext(user)
|
||||
const ThemeContext = createContext(theme)
|
||||
const SettingsContext = createContext(settings)
|
||||
```
|
||||
|
||||
### Context with useMemo
|
||||
|
||||
```typescript
|
||||
const ThemeProvider = ({ children }: Props) => {
|
||||
const [theme, setTheme] = useState('light')
|
||||
|
||||
// Memoize context value to prevent unnecessary re-renders
|
||||
const value = useMemo(() => ({
|
||||
theme,
|
||||
setTheme
|
||||
}), [theme])
|
||||
|
||||
return (
|
||||
<ThemeContext.Provider value={value}>
|
||||
{children}
|
||||
</ThemeContext.Provider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Code Splitting & Lazy Loading
|
||||
|
||||
### React.lazy
|
||||
|
||||
Split components into separate bundles:
|
||||
|
||||
```typescript
|
||||
import { lazy, Suspense } from 'react'
|
||||
|
||||
// Lazy load components
|
||||
const Dashboard = lazy(() => import('./Dashboard'))
|
||||
const Settings = lazy(() => import('./Settings'))
|
||||
const Profile = lazy(() => import('./Profile'))
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<Routes>
|
||||
<Route path="/dashboard" element={<Dashboard />} />
|
||||
<Route path="/settings" element={<Settings />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Route-based Splitting
|
||||
|
||||
```typescript
|
||||
// App.tsx
|
||||
const routes = [
|
||||
{ path: '/', component: lazy(() => import('./pages/Home')) },
|
||||
{ path: '/about', component: lazy(() => import('./pages/About')) },
|
||||
{ path: '/products', component: lazy(() => import('./pages/Products')) },
|
||||
]
|
||||
|
||||
const App = () => (
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<Routes>
|
||||
{routes.map(({ path, component: Component }) => (
|
||||
<Route key={path} path={path} element={<Component />} />
|
||||
))}
|
||||
</Routes>
|
||||
</Suspense>
|
||||
)
|
||||
```
|
||||
|
||||
### Component-based Splitting
|
||||
|
||||
```typescript
|
||||
// Split expensive components
|
||||
const HeavyChart = lazy(() => import('./HeavyChart'))
|
||||
|
||||
const Dashboard = () => {
|
||||
const [showChart, setShowChart] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setShowChart(true)}>
|
||||
Load Chart
|
||||
</button>
|
||||
{showChart && (
|
||||
<Suspense fallback={<ChartSkeleton />}>
|
||||
<HeavyChart />
|
||||
</Suspense>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## List Rendering Optimization
|
||||
|
||||
### Keys
|
||||
|
||||
Always use stable, unique keys:
|
||||
|
||||
```typescript
|
||||
// Bad - index as key (causes issues on reorder/insert)
|
||||
{items.map((item, index) => (
|
||||
<Item key={index} data={item} />
|
||||
))}
|
||||
|
||||
// Good - unique ID as key
|
||||
{items.map(item => (
|
||||
<Item key={item.id} data={item} />
|
||||
))}
|
||||
|
||||
// For static lists without IDs
|
||||
{items.map(item => (
|
||||
<Item key={`${item.name}-${item.category}`} data={item} />
|
||||
))}
|
||||
```
|
||||
|
||||
### Virtualization
|
||||
|
||||
For long lists, render only visible items:
|
||||
|
||||
```typescript
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
|
||||
const VirtualList = ({ items }: { items: Item[] }) => {
|
||||
const parentRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const virtualizer = useVirtualizer({
|
||||
count: items.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => 50, // Estimated item height
|
||||
overscan: 5 // Render 5 extra items above/below viewport
|
||||
})
|
||||
|
||||
return (
|
||||
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
|
||||
<div
|
||||
style={{
|
||||
height: `${virtualizer.getTotalSize()}px`,
|
||||
position: 'relative'
|
||||
}}
|
||||
>
|
||||
{virtualizer.getVirtualItems().map(virtualItem => (
|
||||
<div
|
||||
key={virtualItem.key}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: `${virtualItem.size}px`,
|
||||
transform: `translateY(${virtualItem.start}px)`
|
||||
}}
|
||||
>
|
||||
<Item data={items[virtualItem.index]} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Pagination
|
||||
|
||||
```typescript
|
||||
const PaginatedList = ({ items }: Props) => {
|
||||
const [page, setPage] = useState(1)
|
||||
const itemsPerPage = 20
|
||||
|
||||
const paginatedItems = useMemo(() => {
|
||||
const start = (page - 1) * itemsPerPage
|
||||
const end = start + itemsPerPage
|
||||
return items.slice(start, end)
|
||||
}, [items, page, itemsPerPage])
|
||||
|
||||
return (
|
||||
<>
|
||||
{paginatedItems.map(item => (
|
||||
<Item key={item.id} data={item} />
|
||||
))}
|
||||
<Pagination
|
||||
page={page}
|
||||
total={Math.ceil(items.length / itemsPerPage)}
|
||||
onChange={setPage}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Transitions & Concurrent Features
|
||||
|
||||
### useTransition
|
||||
|
||||
Keep UI responsive during expensive updates:
|
||||
|
||||
```typescript
|
||||
const SearchPage = () => {
|
||||
const [query, setQuery] = useState('')
|
||||
const [results, setResults] = useState([])
|
||||
const [isPending, startTransition] = useTransition()
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setQuery(value) // Urgent - update input immediately
|
||||
|
||||
// Non-urgent - can be interrupted
|
||||
startTransition(() => {
|
||||
const filtered = expensiveFilter(items, value)
|
||||
setResults(filtered)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<input value={query} onChange={e => handleSearch(e.target.value)} />
|
||||
{isPending && <Spinner />}
|
||||
<ResultsList results={results} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### useDeferredValue
|
||||
|
||||
Defer non-urgent renders:
|
||||
|
||||
```typescript
|
||||
const SearchPage = () => {
|
||||
const [query, setQuery] = useState('')
|
||||
const deferredQuery = useDeferredValue(query)
|
||||
|
||||
// Input updates immediately
|
||||
// Results update with deferred value (can be interrupted)
|
||||
const results = useMemo(() => {
|
||||
return expensiveFilter(items, deferredQuery)
|
||||
}, [deferredQuery])
|
||||
|
||||
return (
|
||||
<>
|
||||
<input value={query} onChange={e => setQuery(e.target.value)} />
|
||||
<ResultsList results={results} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Image & Asset Optimization
|
||||
|
||||
### Lazy Load Images
|
||||
|
||||
```typescript
|
||||
const LazyImage = ({ src, alt }: Props) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{!isLoaded && <ImageSkeleton />}
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
loading="lazy" // Native lazy loading
|
||||
onLoad={() => setIsLoaded(true)}
|
||||
className={isLoaded ? 'opacity-100' : 'opacity-0'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Next.js Image Component
|
||||
|
||||
```typescript
|
||||
import Image from 'next/image'
|
||||
|
||||
const OptimizedImage = () => (
|
||||
<Image
|
||||
src="/hero.jpg"
|
||||
alt="Hero"
|
||||
width={800}
|
||||
height={600}
|
||||
priority // Load immediately for above-fold images
|
||||
placeholder="blur"
|
||||
blurDataURL="data:image/jpeg;base64,..."
|
||||
/>
|
||||
)
|
||||
```
|
||||
|
||||
## Bundle Size Optimization
|
||||
|
||||
### Tree Shaking
|
||||
|
||||
Import only what you need:
|
||||
|
||||
```typescript
|
||||
// Bad - imports entire library
|
||||
import _ from 'lodash'
|
||||
|
||||
// Good - import only needed functions
|
||||
import debounce from 'lodash/debounce'
|
||||
import throttle from 'lodash/throttle'
|
||||
|
||||
// Even better - use native methods when possible
|
||||
const debounce = (fn, delay) => {
|
||||
let timeoutId
|
||||
return (...args) => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(() => fn(...args), delay)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Analyze Bundle
|
||||
|
||||
```bash
|
||||
# Next.js
|
||||
ANALYZE=true npm run build
|
||||
|
||||
# Create React App
|
||||
npm install --save-dev webpack-bundle-analyzer
|
||||
```
|
||||
|
||||
### Dynamic Imports
|
||||
|
||||
```typescript
|
||||
// Load library only when needed
|
||||
const handleExport = async () => {
|
||||
const { jsPDF } = await import('jspdf')
|
||||
const doc = new jsPDF()
|
||||
doc.save('report.pdf')
|
||||
}
|
||||
```
|
||||
|
||||
## Common Performance Pitfalls
|
||||
|
||||
### 1. Inline Object Creation
|
||||
|
||||
```typescript
|
||||
// Bad - new object every render
|
||||
<Component style={{ margin: 10 }} />
|
||||
|
||||
// Good - stable reference
|
||||
const style = { margin: 10 }
|
||||
<Component style={style} />
|
||||
|
||||
// Or use useMemo
|
||||
const style = useMemo(() => ({ margin: 10 }), [])
|
||||
```
|
||||
|
||||
### 2. Inline Functions
|
||||
|
||||
```typescript
|
||||
// Bad - new function every render (if child is memoized)
|
||||
<MemoizedChild onClick={() => handleClick(id)} />
|
||||
|
||||
// Good
|
||||
const handleClickMemo = useCallback(() => handleClick(id), [id])
|
||||
<MemoizedChild onClick={handleClickMemo} />
|
||||
```
|
||||
|
||||
### 3. Spreading Props
|
||||
|
||||
```typescript
|
||||
// Bad - causes re-renders even when props unchanged
|
||||
<Component {...props} />
|
||||
|
||||
// Good - pass only needed props
|
||||
<Component value={props.value} onChange={props.onChange} />
|
||||
```
|
||||
|
||||
### 4. Large Context
|
||||
|
||||
```typescript
|
||||
// Bad - everything re-renders on any state change
|
||||
const AppContext = createContext({ user, theme, cart, settings, ... })
|
||||
|
||||
// Good - split into focused contexts
|
||||
const UserContext = createContext(user)
|
||||
const ThemeContext = createContext(theme)
|
||||
const CartContext = createContext(cart)
|
||||
```
|
||||
|
||||
## Performance Checklist
|
||||
|
||||
- [ ] Measure before optimizing (use Profiler)
|
||||
- [ ] Use React DevTools to identify slow components
|
||||
- [ ] Implement code splitting for large routes
|
||||
- [ ] Lazy load below-the-fold content
|
||||
- [ ] Virtualize long lists
|
||||
- [ ] Memoize expensive calculations
|
||||
- [ ] Split large contexts
|
||||
- [ ] Colocate state close to usage
|
||||
- [ ] Use transitions for non-urgent updates
|
||||
- [ ] Optimize images and assets
|
||||
- [ ] Analyze and minimize bundle size
|
||||
- [ ] Remove console.logs in production
|
||||
- [ ] Use production build for testing
|
||||
- [ ] Monitor real-world performance metrics
|
||||
|
||||
## References
|
||||
|
||||
- React Performance: https://react.dev/learn/render-and-commit
|
||||
- React Profiler: https://react.dev/reference/react/Profiler
|
||||
- React Compiler: https://react.dev/reference/react-compiler
|
||||
- Web Vitals: https://web.dev/vitals/
|
||||
|
||||
656
.claude/skills/react/references/server-components.md
Normal file
656
.claude/skills/react/references/server-components.md
Normal file
@@ -0,0 +1,656 @@
|
||||
# React Server Components & Server Functions
|
||||
|
||||
## Overview
|
||||
|
||||
React Server Components (RSC) allow components to render on the server, improving performance and enabling direct data access. Server Functions allow client components to call server-side functions.
|
||||
|
||||
## Server Components
|
||||
|
||||
### What are Server Components?
|
||||
|
||||
Components that run **only on the server**:
|
||||
- Can access databases directly
|
||||
- Zero bundle size (code stays on server)
|
||||
- Better performance (less JavaScript to client)
|
||||
- Automatic code splitting
|
||||
|
||||
### Creating Server Components
|
||||
|
||||
```typescript
|
||||
// app/products/page.tsx
|
||||
// Server Component by default in App Router
|
||||
|
||||
import { db } from '@/lib/db'
|
||||
|
||||
const ProductsPage = async () => {
|
||||
// Direct database access
|
||||
const products = await db.product.findMany({
|
||||
where: { active: true },
|
||||
include: { category: true }
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Products</h1>
|
||||
{products.map(product => (
|
||||
<ProductCard key={product.id} product={product} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ProductsPage
|
||||
```
|
||||
|
||||
### Server Component Rules
|
||||
|
||||
**Can do:**
|
||||
- Access databases and APIs directly
|
||||
- Use server-only modules (fs, path, etc.)
|
||||
- Keep secrets secure (API keys, tokens)
|
||||
- Reduce client bundle size
|
||||
- Use async/await at top level
|
||||
|
||||
**Cannot do:**
|
||||
- Use hooks (useState, useEffect, etc.)
|
||||
- Use browser APIs (window, document)
|
||||
- Attach event handlers (onClick, etc.)
|
||||
- Use Context
|
||||
|
||||
### Mixing Server and Client Components
|
||||
|
||||
```typescript
|
||||
// Server Component (default)
|
||||
const Page = async () => {
|
||||
const data = await fetchData()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ServerComponent data={data} />
|
||||
{/* Client component for interactivity */}
|
||||
<ClientComponent initialData={data} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Client Component
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
|
||||
const ClientComponent = ({ initialData }) => {
|
||||
const [count, setCount] = useState(0)
|
||||
|
||||
return (
|
||||
<button onClick={() => setCount(c => c + 1)}>
|
||||
{count}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Server Component Patterns
|
||||
|
||||
#### Data Fetching
|
||||
```typescript
|
||||
// app/user/[id]/page.tsx
|
||||
interface PageProps {
|
||||
params: { id: string }
|
||||
}
|
||||
|
||||
const UserPage = async ({ params }: PageProps) => {
|
||||
const user = await db.user.findUnique({
|
||||
where: { id: params.id }
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
notFound() // Next.js 404
|
||||
}
|
||||
|
||||
return <UserProfile user={user} />
|
||||
}
|
||||
```
|
||||
|
||||
#### Parallel Data Fetching
|
||||
```typescript
|
||||
const DashboardPage = async () => {
|
||||
// Fetch in parallel
|
||||
const [user, orders, stats] = await Promise.all([
|
||||
fetchUser(),
|
||||
fetchOrders(),
|
||||
fetchStats()
|
||||
])
|
||||
|
||||
return (
|
||||
<>
|
||||
<UserHeader user={user} />
|
||||
<OrdersList orders={orders} />
|
||||
<StatsWidget stats={stats} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Streaming with Suspense
|
||||
```typescript
|
||||
const Page = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<Suspense fallback={<ProductsSkeleton />}>
|
||||
<Products />
|
||||
</Suspense>
|
||||
<Suspense fallback={<ReviewsSkeleton />}>
|
||||
<Reviews />
|
||||
</Suspense>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Products = async () => {
|
||||
const products = await fetchProducts() // Slow query
|
||||
return <ProductsList products={products} />
|
||||
}
|
||||
```
|
||||
|
||||
## Server Functions (Server Actions)
|
||||
|
||||
### What are Server Functions?
|
||||
|
||||
Functions that run on the server but can be called from client components:
|
||||
- Marked with `'use server'` directive
|
||||
- Can mutate data
|
||||
- Integrated with forms
|
||||
- Type-safe with TypeScript
|
||||
|
||||
### Creating Server Functions
|
||||
|
||||
#### File-level directive
|
||||
```typescript
|
||||
// app/actions.ts
|
||||
'use server'
|
||||
|
||||
import { db } from '@/lib/db'
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function createProduct(formData: FormData) {
|
||||
const name = formData.get('name') as string
|
||||
const price = Number(formData.get('price'))
|
||||
|
||||
const product = await db.product.create({
|
||||
data: { name, price }
|
||||
})
|
||||
|
||||
revalidatePath('/products')
|
||||
return product
|
||||
}
|
||||
|
||||
export async function deleteProduct(id: string) {
|
||||
await db.product.delete({ where: { id } })
|
||||
revalidatePath('/products')
|
||||
}
|
||||
```
|
||||
|
||||
#### Function-level directive
|
||||
```typescript
|
||||
// Inside a Server Component
|
||||
const MyComponent = async () => {
|
||||
async function handleSubmit(formData: FormData) {
|
||||
'use server'
|
||||
const email = formData.get('email') as string
|
||||
await saveEmail(email)
|
||||
}
|
||||
|
||||
return <form action={handleSubmit}>...</form>
|
||||
}
|
||||
```
|
||||
|
||||
### Using Server Functions
|
||||
|
||||
#### With Forms
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { createProduct } from './actions'
|
||||
|
||||
const ProductForm = () => {
|
||||
return (
|
||||
<form action={createProduct}>
|
||||
<input name="name" required />
|
||||
<input name="price" type="number" required />
|
||||
<button type="submit">Create</button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### With useActionState
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { useActionState } from 'react'
|
||||
import { createProduct } from './actions'
|
||||
|
||||
type FormState = {
|
||||
message: string
|
||||
success: boolean
|
||||
} | null
|
||||
|
||||
const ProductForm = () => {
|
||||
const [state, formAction, isPending] = useActionState<FormState>(
|
||||
async (previousState, formData: FormData) => {
|
||||
try {
|
||||
await createProduct(formData)
|
||||
return { message: 'Product created!', success: true }
|
||||
} catch (error) {
|
||||
return { message: 'Failed to create product', success: false }
|
||||
}
|
||||
},
|
||||
null
|
||||
)
|
||||
|
||||
return (
|
||||
<form action={formAction}>
|
||||
<input name="name" required />
|
||||
<input name="price" type="number" required />
|
||||
<button disabled={isPending}>
|
||||
{isPending ? 'Creating...' : 'Create'}
|
||||
</button>
|
||||
{state?.message && (
|
||||
<p className={state.success ? 'text-green-600' : 'text-red-600'}>
|
||||
{state.message}
|
||||
</p>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
#### Programmatic Invocation
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { deleteProduct } from './actions'
|
||||
|
||||
const DeleteButton = ({ productId }: { productId: string }) => {
|
||||
const [isPending, setIsPending] = useState(false)
|
||||
|
||||
const handleDelete = async () => {
|
||||
setIsPending(true)
|
||||
try {
|
||||
await deleteProduct(productId)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
setIsPending(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={handleDelete} disabled={isPending}>
|
||||
{isPending ? 'Deleting...' : 'Delete'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Server Function Patterns
|
||||
|
||||
#### Validation with Zod
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
import { z } from 'zod'
|
||||
|
||||
const ProductSchema = z.object({
|
||||
name: z.string().min(3),
|
||||
price: z.number().positive(),
|
||||
description: z.string().optional()
|
||||
})
|
||||
|
||||
export async function createProduct(formData: FormData) {
|
||||
const rawData = {
|
||||
name: formData.get('name'),
|
||||
price: Number(formData.get('price')),
|
||||
description: formData.get('description')
|
||||
}
|
||||
|
||||
// Validate
|
||||
const result = ProductSchema.safeParse(rawData)
|
||||
if (!result.success) {
|
||||
return {
|
||||
success: false,
|
||||
errors: result.error.flatten().fieldErrors
|
||||
}
|
||||
}
|
||||
|
||||
// Create product
|
||||
const product = await db.product.create({
|
||||
data: result.data
|
||||
})
|
||||
|
||||
revalidatePath('/products')
|
||||
return { success: true, product }
|
||||
}
|
||||
```
|
||||
|
||||
#### Authentication Check
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
import { auth } from '@/lib/auth'
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createOrder(formData: FormData) {
|
||||
const session = await auth()
|
||||
|
||||
if (!session?.user) {
|
||||
redirect('/login')
|
||||
}
|
||||
|
||||
const order = await db.order.create({
|
||||
data: {
|
||||
userId: session.user.id,
|
||||
// ... other fields
|
||||
}
|
||||
})
|
||||
|
||||
return order
|
||||
}
|
||||
```
|
||||
|
||||
#### Error Handling
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
export async function updateProfile(formData: FormData) {
|
||||
try {
|
||||
const userId = await getCurrentUserId()
|
||||
|
||||
const profile = await db.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
name: formData.get('name') as string,
|
||||
bio: formData.get('bio') as string
|
||||
}
|
||||
})
|
||||
|
||||
revalidatePath('/profile')
|
||||
return { success: true, profile }
|
||||
} catch (error) {
|
||||
console.error('Failed to update profile:', error)
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to update profile. Please try again.'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Optimistic Updates
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { useOptimistic } from 'react'
|
||||
import { likePost } from './actions'
|
||||
|
||||
const Post = ({ post }: { post: Post }) => {
|
||||
const [optimisticLikes, addOptimisticLike] = useOptimistic(
|
||||
post.likes,
|
||||
(currentLikes) => currentLikes + 1
|
||||
)
|
||||
|
||||
const handleLike = async () => {
|
||||
addOptimisticLike(null)
|
||||
await likePost(post.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>{post.content}</p>
|
||||
<button onClick={handleLike}>
|
||||
❤️ {optimisticLikes}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Data Mutations & Revalidation
|
||||
|
||||
### revalidatePath
|
||||
Invalidate cached data for a path:
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
import { revalidatePath } from 'next/cache'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
await db.post.create({ data: {...} })
|
||||
|
||||
// Revalidate the posts page
|
||||
revalidatePath('/posts')
|
||||
|
||||
// Revalidate with layout
|
||||
revalidatePath('/posts', 'layout')
|
||||
}
|
||||
```
|
||||
|
||||
### revalidateTag
|
||||
Invalidate cached data by tag:
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
import { revalidateTag } from 'next/cache'
|
||||
|
||||
export async function updateProduct(id: string, data: ProductData) {
|
||||
await db.product.update({ where: { id }, data })
|
||||
|
||||
// Revalidate all queries tagged with 'products'
|
||||
revalidateTag('products')
|
||||
}
|
||||
```
|
||||
|
||||
### redirect
|
||||
Redirect after mutation:
|
||||
|
||||
```typescript
|
||||
'use server'
|
||||
|
||||
import { redirect } from 'next/navigation'
|
||||
|
||||
export async function createPost(formData: FormData) {
|
||||
const post = await db.post.create({ data: {...} })
|
||||
|
||||
// Redirect to the new post
|
||||
redirect(`/posts/${post.id}`)
|
||||
}
|
||||
```
|
||||
|
||||
## Caching with Server Components
|
||||
|
||||
### cache Function
|
||||
Deduplicate requests within a render:
|
||||
|
||||
```typescript
|
||||
import { cache } from 'react'
|
||||
|
||||
export const getUser = cache(async (id: string) => {
|
||||
return await db.user.findUnique({ where: { id } })
|
||||
})
|
||||
|
||||
// Called multiple times but only fetches once per render
|
||||
const Page = async () => {
|
||||
const user1 = await getUser('123')
|
||||
const user2 = await getUser('123') // Uses cached result
|
||||
|
||||
return <div>...</div>
|
||||
}
|
||||
```
|
||||
|
||||
### Next.js fetch Caching
|
||||
```typescript
|
||||
// Cached by default
|
||||
const data = await fetch('https://api.example.com/data')
|
||||
|
||||
// Revalidate every 60 seconds
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
next: { revalidate: 60 }
|
||||
})
|
||||
|
||||
// Never cache
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
cache: 'no-store'
|
||||
})
|
||||
|
||||
// Tag for revalidation
|
||||
const data = await fetch('https://api.example.com/data', {
|
||||
next: { tags: ['products'] }
|
||||
})
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Component Placement
|
||||
- Keep interactive components client-side
|
||||
- Use server components for data fetching
|
||||
- Place 'use client' as deep as possible in tree
|
||||
|
||||
### 2. Data Fetching
|
||||
- Fetch in parallel when possible
|
||||
- Use Suspense for streaming
|
||||
- Cache expensive operations
|
||||
|
||||
### 3. Server Functions
|
||||
- Validate all inputs
|
||||
- Check authentication/authorization
|
||||
- Handle errors gracefully
|
||||
- Return serializable data only
|
||||
|
||||
### 4. Performance
|
||||
- Minimize client JavaScript
|
||||
- Use streaming for slow queries
|
||||
- Implement proper caching
|
||||
- Optimize database queries
|
||||
|
||||
### 5. Security
|
||||
- Never expose secrets to client
|
||||
- Validate server function inputs
|
||||
- Use environment variables
|
||||
- Implement rate limiting
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Layout with Dynamic Data
|
||||
```typescript
|
||||
// app/layout.tsx
|
||||
const RootLayout = async ({ children }: { children: React.ReactNode }) => {
|
||||
const user = await getCurrentUser()
|
||||
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<Header user={user} />
|
||||
{children}
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Loading States
|
||||
```typescript
|
||||
// app/products/loading.tsx
|
||||
export default function Loading() {
|
||||
return <ProductsSkeleton />
|
||||
}
|
||||
|
||||
// app/products/page.tsx
|
||||
const ProductsPage = async () => {
|
||||
const products = await fetchProducts()
|
||||
return <ProductsList products={products} />
|
||||
}
|
||||
```
|
||||
|
||||
### Error Boundaries
|
||||
```typescript
|
||||
// app/products/error.tsx
|
||||
'use client'
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset
|
||||
}: {
|
||||
error: Error
|
||||
reset: () => void
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<h2>Something went wrong!</h2>
|
||||
<p>{error.message}</p>
|
||||
<button onClick={reset}>Try again</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Search with Server Functions
|
||||
```typescript
|
||||
'use client'
|
||||
|
||||
import { searchProducts } from './actions'
|
||||
import { useDeferredValue, useState, useEffect } from 'react'
|
||||
|
||||
const SearchPage = () => {
|
||||
const [query, setQuery] = useState('')
|
||||
const [results, setResults] = useState([])
|
||||
const deferredQuery = useDeferredValue(query)
|
||||
|
||||
useEffect(() => {
|
||||
if (deferredQuery) {
|
||||
searchProducts(deferredQuery).then(setResults)
|
||||
}
|
||||
}, [deferredQuery])
|
||||
|
||||
return (
|
||||
<>
|
||||
<input
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
/>
|
||||
<ResultsList results={results} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **"Cannot use hooks in Server Component"**
|
||||
- Add 'use client' directive
|
||||
- Move state logic to client component
|
||||
|
||||
2. **"Functions cannot be passed to Client Components"**
|
||||
- Use Server Functions instead
|
||||
- Pass data, not functions
|
||||
|
||||
3. **Hydration mismatches**
|
||||
- Ensure server and client render same HTML
|
||||
- Use useEffect for browser-only code
|
||||
|
||||
4. **Slow initial load**
|
||||
- Implement Suspense boundaries
|
||||
- Use streaming rendering
|
||||
- Optimize database queries
|
||||
|
||||
## References
|
||||
|
||||
- React Server Components: https://react.dev/reference/rsc/server-components
|
||||
- Server Functions: https://react.dev/reference/rsc/server-functions
|
||||
- Next.js App Router: https://nextjs.org/docs/app
|
||||
|
||||
Reference in New Issue
Block a user