Basic Form Submission
'use client';
import { useState } from 'react';
export default function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
setError(null);
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});
if (!response.ok) {
throw new Error('Failed to submit form');
}
const result = await response.json();
console.log('Success:', result);
setFormData({ name: '', email: '', message: '' });
} catch (err) {
setError(err.message);
} finally {
setIsSubmitting(false);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
};
return (
<form onSubmit={handleSubmit}>
{error && <div className="error">{error}</div>}
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
placeholder="Your name"
required
/>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Your email"
required
/>
<textarea
name="message"
value={formData.message}
onChange={handleChange}
placeholder="Your message"
required
/>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Sending...' : 'Send Message'}
</button>
</form>
);
}
Optimistic Updates
'use client';
import { useState } from 'react';
export default function TodoList({ initialTodos }) {
const [todos, setTodos] = useState(initialTodos);
const addTodo = async (text) => {
const newTodo = {
id: Date.now(),
text,
completed: false
};
setTodos(prev => [...prev, newTodo]);
try {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text }),
});
if (!response.ok) {
throw new Error('Failed to add todo');
}
const savedTodo = await response.json();
setTodos(prev =>
prev.map(todo =>
todo.id === newTodo.id ? savedTodo : todo
)
);
} catch (error) {
setTodos(prev => prev.filter(todo => todo.id !== newTodo.id));
console.error('Error adding todo:', error);
}
};
const toggleTodo = async (id) => {
const todo = todos.find(t => t.id === id);
setTodos(prev =>
prev.map(t =>
t.id === id ? { ...t, completed: !t.completed } : t
)
);
try {
const response = await fetch(`/api/todos/${id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: !todo.completed }),
});
if (!response.ok) {
throw new Error('Failed to update todo');
}
} catch (error) {
setTodos(prev =>
prev.map(t =>
t.id === id ? { ...t, completed: todo.completed } : t
)
);
console.error('Error updating todo:', error);
}
};
return (
<div>
<form onSubmit={(e) => {
e.preventDefault();
const text = e.target.todo.value;
if (text.trim()) {
addTodo(text);
e.target.todo.value = '';
}
}}>
<input name="todo" placeholder="Add new todo" />
<button type="submit">Add</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
</li>
))}
</ul>
</div>
);
}
Basic Server Action
'use server';
import { revalidatePath } from 'next/cache';
export async function addTodo(formData) {
const text = formData.get('text');
if (!text || text.trim().length === 0) {
return { error: 'Todo text is required' };
}
try {
const todo = await db.todo.create({
data: { text: text.trim() }
});
revalidatePath('/todos');
return { success: true, todo };
} catch (error) {
return { error: 'Failed to add todo' };
}
}
export async function updateTodo(id, completed) {
try {
await db.todo.update({
where: { id },
data: { completed }
});
revalidatePath('/todos');
return { success: true };
} catch (error) {
return { error: 'Failed to update todo' };
}
}
export async function deleteTodo(id) {
try {
await db.todo.delete({
where: { id }
});
revalidatePath('/todos');
return { success: true };
} catch (error) {
return { error: 'Failed to delete todo' };
}
}
Using Server Actions in Components
import { addTodo, updateTodo, deleteTodo } from '@/app/actions/todoActions';
export default function TodosPage() {
return (
<div>
<form action={addTodo}>
<input
name="text"
placeholder="Add new todo"
required
/>
<button type="submit">Add Todo</button>
</form>
<TodoList />
</div>
);
}
'use client';
import { useTransition } from 'react';
import { updateTodo, deleteTodo } from '@/app/actions/todoActions';
export default function TodoList({ todos }) {
const [isPending, startTransition] = useTransition();
const handleToggle = (id, completed) => {
startTransition(() => {
updateTodo(id, !completed);
});
};
const handleDelete = (id) => {
startTransition(() => {
deleteTodo(id);
});
};
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => handleToggle(todo.id, todo.completed)}
disabled={isPending}
/>
<span style={{
textDecoration: todo.completed ? 'line-through' : 'none'
}}>
{todo.text}
</span>
<button
onClick={() => handleDelete(todo.id)}
disabled={isPending}
>
Delete
</button>
</li>
))}
{isPending && <div>Updating...</div>}
</ul>
);
}
CRUD API Routes
import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';
export async function GET() {
try {
const todos = await db.todo.findMany({
orderBy: { createdAt: 'desc' }
});
return NextResponse.json(todos);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch todos' },
{ status: 500 }
);
}
}
export async function POST(request) {
try {
const { text } = await request.json();
if (!text || text.trim().length === 0) {
return NextResponse.json(
{ error: 'Todo text is required' },
{ status: 400 }
);
}
const todo = await db.todo.create({
data: { text: text.trim() }
});
revalidatePath('/todos');
return NextResponse.json(todo, { status: 201 });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create todo' },
{ status: 500 }
);
}
}
Dynamic API Routes
import { NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';
export async function GET(request, { params }) {
try {
const todo = await db.todo.findUnique({
where: { id: params.id }
});
if (!todo) {
return NextResponse.json(
{ error: 'Todo not found' },
{ status: 404 }
);
}
return NextResponse.json(todo);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to fetch todo' },
{ status: 500 }
);
}
}
export async function PATCH(request, { params }) {
try {
const { text, completed } = await request.json();
const todo = await db.todo.update({
where: { id: params.id },
data: {
...(text !== undefined && { text }),
...(completed !== undefined && { completed })
}
});
revalidatePath('/todos');
revalidatePath(`/todos/${params.id}`);
return NextResponse.json(todo);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to update todo' },
{ status: 500 }
);
}
}
export async function DELETE(request, { params }) {
try {
await db.todo.delete({
where: { id: params.id }
});
revalidatePath('/todos');
return NextResponse.json({ success: true });
} catch (error) {
return NextResponse.json(
{ error: 'Failed to delete todo' },
{ status: 500 }
);
}
}
Cache Invalidation
import { revalidatePath, revalidateTag } from 'next/cache';
export async function invalidateTodos() {
revalidatePath('/todos');
revalidatePath('/dashboard');
revalidateTag('todos');
}
export async function POST(request) {
try {
const { text } = await request.json();
const todo = await db.todo.create({
data: { text }
});
await invalidateTodos();
return NextResponse.json(todo);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create todo' },
{ status: 500 }
);
}
}
Conditional Revalidation
export async function POST(request) {
try {
const { title, content, category } = await request.json();
const post = await db.post.create({
data: { title, content, category }
});
revalidatePath('/posts');
revalidatePath(`/category/${category}`);
if (post.featured) {
revalidatePath('/');
}
return NextResponse.json(post);
} catch (error) {
return NextResponse.json(
{ error: 'Failed to create post' },
{ status: 500 }
);
}
}
WebSocket Integration
'use client';
import { useEffect, useState } from 'react';
export default function RealTimeTodos() {
const [todos, setTodos] = useState([]);
const [ws, setWs] = useState(null);
useEffect(() => {
const socket = new WebSocket('ws://localhost:3001');
socket.onopen = () => {
console.log('Connected to WebSocket');
};
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'TODO_ADDED') {
setTodos(prev => [...prev, data.todo]);
} else if (data.type === 'TODO_UPDATED') {
setTodos(prev =>
prev.map(todo =>
todo.id === data.todo.id ? data.todo : todo
)
);
} else if (data.type === 'TODO_DELETED') {
setTodos(prev =>
prev.filter(todo => todo.id !== data.todoId)
);
}
};
socket.onclose = () => {
console.log('Disconnected from WebSocket');
};
setWs(socket);
return () => {
socket.close();
};
}, []);
const addTodo = async (text) => {
const response = await fetch('/api/todos', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
if (response.ok) {
console.log('Todo added, waiting for real-time update');
}
};
return (
<div>
<form onSubmit={(e) => {
e.preventDefault();
const text = e.target.todo.value;
if (text.trim()) {
addTodo(text);
e.target.todo.value = '';
}
}}>
<input name="todo" placeholder="Add new todo" />
<button type="submit">Add</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
</li>
))}
</ul>
</div>
);
}
Form Validation
'use client';
import { useState } from 'react';
export default function ValidatedForm() {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const validateForm = () => {
const newErrors = {};
if (!formData.email) {
newErrors.email = 'Email is required';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = 'Email is invalid';
}
if (!formData.password) {
newErrors.password = 'Password is required';
} else if (formData.password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e) => {
e.preventDefault();
if (!validateForm()) {
return;
}
setIsSubmitting(true);
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (!response.ok) {
const errorData = await response.json();
setErrors(errorData.errors || { general: 'Registration failed' });
return;
}
console.log('Registration successful');
} catch (error) {
setErrors({ general: 'Network error' });
} finally {
setIsSubmitting(false);
}
};
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({
...prev,
[name]: value
}));
if (errors[name]) {
setErrors(prev => ({
...prev,
[name]: ''
}));
}
};
return (
<form onSubmit={handleSubmit}>
{errors.general && (
<div className="error">{errors.general}</div>
)}
<div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
placeholder="Email"
className={errors.email ? 'error' : ''}
/>
{errors.email && <span className="error-text">{errors.email}</span>}
</div>
<div>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
placeholder="Password"
className={errors.password ? 'error' : ''}
/>
{errors.password && <span className="error-text">{errors.password}</span>}
</div>
<div>
<input
type="password"
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
placeholder="Confirm Password"
className={errors.confirmPassword ? 'error' : ''}
/>
{errors.confirmPassword && (
<span className="error-text">{errors.confirmPassword}</span>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Registering...' : 'Register'}
</button>
</form>
);
}
Debounced Updates
'use client';
import { useState, useEffect, useCallback } from 'react';
export default function DebouncedInput({ initialValue, onSave }) {
const [value, setValue] = useState(initialValue);
const [debouncedValue, setDebouncedValue] = useState(initialValue);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, 500);
return () => clearTimeout(timer);
}, [value]);
useEffect(() => {
if (debouncedValue !== initialValue) {
onSave(debouncedValue);
}
}, [debouncedValue, onSave, initialValue]);
return (
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="Type to search..."
/>
);
}
Batch Updates
'use client';
import { useState, useCallback } from 'react';
export default function BatchUpdater() {
const [updates, setUpdates] = useState([]);
const [isProcessing, setIsProcessing] = useState(false);
const addUpdate = useCallback((update) => {
setUpdates(prev => [...prev, update]);
}, []);
const processBatch = async () => {
if (updates.length === 0) return;
setIsProcessing(true);
try {
const response = await fetch('/api/batch-update', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ updates })
});
if (response.ok) {
setUpdates([]);
console.log('Batch update successful');
}
} catch (error) {
console.error('Batch update failed:', error);
} finally {
setIsProcessing(false);
}
};
return (
<div>
<button onClick={() => addUpdate({ type: 'increment', id: 1 })}>
Add Update
</button>
<button
onClick={processBatch}
disabled={updates.length === 0 || isProcessing}
>
{isProcessing ? 'Processing...' : `Process ${updates.length} Updates`}
</button>
<div>
Pending updates: {updates.length}
</div>
</div>
);
}