Next.js Client Components — When to Use Them
Advertisement
Next.js Client Components — When to Use Them
Client Components handle interactivity. They run in the browser where users can interact with them using React hooks and browser APIs.
- Next.js Client Components — When to Use Them
- What Are Client Components?
- When to Use Client Components
- Hooks in Client Components
- Context in Client Components
- Effects and Lifecycle
- Event Handling
- Performance Optimization
- Real-World Example: Form with Validation
- FAQ
What Are Client Components?
Client Components are marked with the 'use client' directive. They:
- Run entirely in the browser
- Can use React hooks like useState, useEffect
- Can access browser APIs like localStorage, geolocation
- Handle user interactions and real-time updates
// components/counter.tsx
'use client'
import { useState } from 'react'
export function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
)
}
When to Use Client Components
Use Client Components when you need:
1. State Management
'use client'
export function SearchForm() {
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<Results items={results} />
</div>
)
}
2. Browser APIs
'use client'
import { useEffect, useState } from 'react'
export function LocationTracker() {
const [location, setLocation] = useState(null)
useEffect(() => {
navigator.geolocation.getCurrentPosition((pos) => {
setLocation(pos.coords)
})
}, [])
return <div>Lat: {location?.latitude}, Lng: {location?.longitude}</div>
}
3. Event Listeners
'use client'
import { useEffect } from 'react'
export function WindowSize() {
useEffect(() => {
function handleResize() {
console.log(`Window size: ${window.innerWidth}x${window.innerHeight}`)
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return <div>Watch console for size changes</div>
}
Hooks in Client Components
All React hooks work in Client Components:
'use client'
import { useState, useEffect, useCallback, useMemo } from 'react'
export function AdvancedForm() {
const [formData, setFormData] = useState({ name: '', email: '' })
const [isSubmitting, setIsSubmitting] = useState(false)
const isValid = useMemo(() => {
return formData.name.length > 0 && formData.email.includes('@')
}, [formData])
const handleSubmit = useCallback(async (e) => {
e.preventDefault()
setIsSubmitting(true)
try {
await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(formData)
})
} finally {
setIsSubmitting(false)
}
}, [formData])
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
/>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
/>
<button disabled={!isValid || isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
)
}
Context in Client Components
Use React Context for state sharing:
'use client'
import { createContext, useContext, useState } from 'react'
const ThemeContext = createContext()
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const toggleTheme = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
}
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
return useContext(ThemeContext)
}
Effects and Lifecycle
useEffect handles side effects:
'use client'
import { useEffect, useState } from 'react'
export function PostList() {
const [posts, setPosts] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchPosts = async () => {
try {
const res = await fetch('/api/posts')
const data = await res.json()
setPosts(data)
} finally {
setLoading(false)
}
}
fetchPosts()
}, [])
if (loading) return <div>Loading...</div>
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
}
Event Handling
Client Components handle user interactions:
'use client'
export function InteractiveButton() {
const handleClick = () => alert('Button clicked!')
const handleMouseEnter = () => console.log('Mouse entered')
const handleChange = (e) => console.log('Input:', e.target.value)
return (
<div>
<button onClick={handleClick}>Click me</button>
<button onMouseEnter={handleMouseEnter}>Hover me</button>
<input onChange={handleChange} />
</div>
)
}
Performance Optimization
Use memoization to prevent unnecessary re-renders:
'use client'
import { memo, useMemo, useCallback } from 'react'
const ExpensiveComponent = memo(({ data }) => {
return <div>{/* Expensive render */}</div>
})
export function ParentComponent() {
const memoizedData = useMemo(() => computeData(), [])
const memoizedCallback = useCallback(() => handleAction(), [])
return (
<div>
<ExpensiveComponent data={memoizedData} />
<button onClick={memoizedCallback}>Action</button>
</div>
)
}
Real-World Example: Form with Validation
'use client'
import { useState, useCallback } from 'react'
export function RegistrationForm() {
const [formData, setFormData] = useState({
username: '',
email: '',
password: ''
})
const [errors, setErrors] = useState({})
const handleChange = useCallback((e) => {
const { name, value } = e.target
setFormData(prev => ({ ...prev, [name]: value }))
}, [])
const handleSubmit = useCallback(async (e) => {
e.preventDefault()
const newErrors = {}
if (!formData.username) newErrors.username = 'Required'
if (!formData.email.includes('@')) newErrors.email = 'Invalid email'
if (formData.password.length < 8) newErrors.password = 'Min 8 chars'
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors)
return
}
// Submit form
await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(formData)
})
}, [formData])
return (
<form onSubmit={handleSubmit}>
<input
name="username"
value={formData.username}
onChange={handleChange}
/>
{errors.username && <span>{errors.username}</span>}
<input
name="email"
type="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
<input
name="password"
type="password"
value={formData.password}
onChange={handleChange}
/>
{errors.password && <span>{errors.password}</span>}
<button type="submit">Register</button>
</form>
)
}
FAQ
Q: When should I use Client Components vs Server Components? A: Server Components by default. Use Client Components only for interactivity, state, or browser APIs.
Q: Can I use hooks in a Server Component? A: No, hooks are React features that only work in Client Components.
Q: Should I wrap my entire app in 'use client'? A: No, that defeats the benefits of Server Components. Use 'use client' granularly for components that need it.
Master Client Components and you'll build responsive, interactive experiences efficiently.
Advertisement