| name | react |
| description | React 19 hooks patterns and component best practices for this project |
React 19 Skill
Overview
This project uses React 19.2.3 with TypeScript and functional components. React 19 brings major improvements including the React Compiler for automatic optimization, new hooks for better data handling, and enhanced form capabilities.
Project Configuration
Detected Version: React 19.2.3
React DOM: 19.2.3
Component Style: Functional components with hooks
TypeScript: Strict mode enabled
Compiler: React Compiler (built-in with Next.js 16)
React 19 New Features
1. React Compiler (Automatic Memoization)
The React Compiler automatically optimizes components:
- No manual
useMemo - compiler handles it
- No manual
useCallback - compiler handles it
- Automatic re-render optimization
- Enabled by default in Next.js 16
const filteredTasks = useMemo(() => {
return tasks.filter(task => task.completed);
}, [tasks]);
const filteredTasks = tasks.filter(task => task.completed);
When to still use manual memoization:
- Use React Profiler to measure performance
- Only add manual memoization if profiling shows clear benefit
- Avoid premature optimization
2. New Hooks
use() Hook
Simplifies async data and context consumption:
import { use } from 'react';
const theme = use(ThemeContext);
const data = use(fetchDataPromise);
Note: For this client-side task manager, we won't use use() for data fetching since we're managing state locally with useState.
useFormStatus()
Provides form submission status:
'use client';
import { useFormStatus } from 'react';
function SubmitButton() {
const { pending, data, method } = useFormStatus();
return (
<button disabled={pending}>
{pending ? 'Adding...' : 'Add Task'}
</button>
);
}
useOptimistic()
Handles optimistic UI updates:
'use client';
import { useOptimistic } from 'react';
function TaskList({ tasks }) {
const [optimisticTasks, addOptimisticTask] = useOptimistic(
tasks,
(state, newTask) => [...state, { ...newTask, pending: true }]
);
async function addTask(task) {
addOptimisticTask(task);
await saveTask(task);
}
return optimisticTasks.map(task => (
<TaskItem key={task.id} task={task} />
));
}
Note: For this client-side app, optimistic updates aren't needed since state changes are synchronous.
Component Patterns (2026)
1. Functional Components with TypeScript
Always use explicit prop interfaces:
interface TaskInputProps {
onAddTask: (description: string) => void;
}
export default function TaskInput({ onAddTask }: TaskInputProps) {
const [description, setDescription] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onAddTask(description);
setDescription('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<button type="submit">Add</button>
</form>
);
}
2. State Management with useState
For local component state:
'use client';
import { useState } from 'react';
export default function TaskList() {
const [tasks, setTasks] = useState<Task[]>([]);
const [filter, setFilter] = useState<'all' | 'active' | 'completed'>('all');
const addTask = (description: string) => {
setTasks([...tasks, {
id: crypto.randomUUID(),
description,
completed: false,
createdAt: new Date(),
}]);
};
const toggleTask = (id: string) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id: string) => {
setTasks(tasks.filter(task => task.id !== id));
};
return ();
}
3. useEffect Best Practices (React 19)
useEffect is for real side effects only:
useEffect(() => {
document.title = `${tasks.length} tasks remaining`;
}, [tasks]);
useEffect(() => {
const subscription = eventSource.subscribe(handleEvent);
return () => subscription.unsubscribe();
}, []);
useEffect(() => {
fetch('/api/tasks').then();
}, []);
For this project: Use useEffect for:
- Setting document title
- Focus management
- Local storage sync (if added)
Don't use for:
- Data fetching (we're using client-side state)
- Derived state (React Compiler handles it)
4. Refs for DOM Access
import { useRef, useEffect } from 'react';
export default function TaskInput() {
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
inputRef.current?.focus();
};
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} />;
}
Custom Hooks
Extract domain logic into custom hooks:
'use client';
import { useState } from 'react';
export function useTasks() {
const [tasks, setTasks] = useState<Task[]>([]);
const addTask = (description: string) => {
setTasks([...tasks, {
id: crypto.randomUUID(),
description,
completed: false,
createdAt: new Date(),
}]);
};
const toggleTask = (id: string) => {
setTasks(tasks.map(task =>
task.id === id ? { ...task, completed: !task.completed } : task
));
};
const deleteTask = (id: string) => {
setTasks(tasks.filter(task => task.id !== id));
};
return { tasks, addTask, toggleTask, deleteTask };
}
export default function TaskList() {
const { tasks, addTask, toggleTask, deleteTask } = useTasks();
return ();
}
Benefits:
- Separates business logic from UI
- Reusable across components
- Easier to test
Event Handling
Form Events
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value);
};
Click Events
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.stopPropagation();
};
Keyboard Events
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
handleSubmit();
}
if (e.key === 'Escape') {
handleCancel();
}
};
Conditional Rendering
{tasks.length === 0 && (
<p>No tasks yet. Add one above!</p>
)}
{loading ? (
<Spinner />
) : (
<TaskList tasks={tasks} />
)}
{tasks.length === 0 ? (
<EmptyState />
) : filter === 'active' ? (
<ActiveTasks tasks={activeTasks} />
) : (
<CompletedTasks tasks={completedTasks} />
)}
Lists and Keys
{tasks.map(task => (
<TaskItem
key={task.id} // Use stable ID, not index
task={task}
onToggle={toggleTask}
onDelete={deleteTask}
/>
))}
Key Rules:
- Use unique, stable IDs (not array index)
- Keys must be unique among siblings
- Don't use
Math.random() for keys
Common Patterns
1. Controlled Inputs
const [value, setValue] = useState('');
<input
type="text"
value={value}
onChange={(e) => setValue(e.target.value)}
/>
2. Lifting State Up
function TaskManager() {
const [tasks, setTasks] = useState<Task[]>([]);
return (
<>
<TaskInput onAddTask={(desc) => {/* add to tasks */}} />
<TaskList tasks={tasks} />
</>
);
}
3. Composition
function TaskList({ tasks, children }) {
return (
<div>
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
{children}
</div>
);
}
<TaskList tasks={tasks}>
<TaskStats count={tasks.length} />
</TaskList>
Performance Best Practices (2026)
1. Let React Compiler Handle Optimization
const filteredTasks = tasks.filter(task => {
if (filter === 'active') return !task.completed;
if (filter === 'completed') return task.completed;
return true;
});
const filteredTasks = useMemo(() => {
return tasks.filter();
}, [tasks, filter]);
2. Measure Before Optimizing
import { Profiler } from 'react';
<Profiler id="TaskList" onRender={onRenderCallback}>
<TaskList tasks={tasks} />
</Profiler>
3. Code Splitting (if needed later)
import { lazy, Suspense } from 'react';
const HeavyComponent = lazy(() => import('./HeavyComponent'));
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
Accessibility
ARIA Labels
<button
onClick={handleDelete}
aria-label={`Delete task "${task.description}"`}
>
🗑️
</button>
<input
type="checkbox"
checked={task.completed}
onChange={handleToggle}
aria-label={`Mark "${task.description}" as ${task.completed ? 'incomplete' : 'complete'}`}
/>
Keyboard Navigation
<button
onClick={handleAction}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAction();
}
}}
>
Action
</button>
Focus Management
const inputRef = useRef<HTMLInputElement>(null);
const handleSubmit = () => {
inputRef.current?.focus();
};
Common Pitfalls (2026)
❌ Don't: Mutate state directly
tasks.push(newTask);
setTasks(tasks);
setTasks([...tasks, newTask]);
❌ Don't: Use index as key
{tasks.map((task, index) => (
<TaskItem key={index} task={task} />
))}
{tasks.map(task => (
<TaskItem key={task.id} task={task} />
))}
❌ Don't: Call hooks conditionally
if (condition) {
const [state, setState] = useState(0);
}
const [state, setState] = useState(0);
if (condition) {
}
❌ Don't: Forget dependency arrays
useEffect(() => {
console.log(tasks);
});
useEffect(() => {
console.log(tasks);
}, [tasks]);
TypeScript Integration
Component Props
interface TaskItemProps {
task: Task;
onToggle: (id: string) => void;
onDelete: (id: string) => void;
}
export default function TaskItem({ task, onToggle, onDelete }: TaskItemProps) {
return ();
}
Event Handlers
const handleClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
};
const handleChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
};
Children Props
interface ContainerProps {
children: React.ReactNode;
}
function Container({ children }: ContainerProps) {
return <div>{children}</div>;
}
References