React Development Skill
This skill provides comprehensive guidance for building modern React applications using hooks, components, state management, context, effects, and performance optimization techniques based on official React documentation from react.dev.
When to Use This Skill
Use this skill when:
Building single-page applications (SPAs) with React Creating reusable UI components and component libraries Managing complex application state with hooks and context Implementing forms, data fetching, and side effects Optimizing React application performance Building interactive user interfaces with dynamic data Migrating class components to functional components with hooks Implementing global state management without external libraries Creating custom hooks for reusable logic Building accessible and performant web applications Core Concepts Components
Components are the building blocks of React applications. They let you split the UI into independent, reusable pieces.
Functional Components (Modern Approach):
function Welcome(props) { return
Hello, {props.name}
; }// Arrow function syntax const Greeting = ({ name, age }) => { return (
{name}
Age: {age}
Component Composition:
function App() { return (
JSX
JSX is a syntax extension for JavaScript that looks similar to HTML. It produces React elements.
JSX Fundamentals:
// Embedding expressions const name = 'Josh Perez'; const element =
Hello, {name}
;// JSX attributes
const image = ;
// JSX children
const container = (
Welcome
Get started with React
);
// Conditional rendering
const greeting = (
);
// Lists and keys
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
);
Props
Props are arguments passed into React components. They are passed to components via HTML attributes.
Passing and Using Props:
function Product({ name, price, inStock }) { return (
{name}
${price}
{inStock ? In Stock : Out of Stock}// Usage
Props with Children:
function Card({ title, children }) { return (
{title}
// Usage
This is the card content
Default Props:
function Button({ text = 'Click me', variant = 'primary' }) { return ; }
State
State is a component's memory. It lets components remember information and respond to user interactions.
Local Component State:
import { useState } from 'react';
function Counter() { const [count, setCount] = useState(0);
return (
Count: {count}
React Hooks
Hooks let you use state and other React features in functional components.
useState
The useState hook lets you add state to functional components.
Basic Usage:
import { useState } from 'react';
function Form() { const [name, setName] = useState(''); const [email, setEmail] = useState('');
const handleSubmit = (e) => { e.preventDefault(); console.log({ name, email }); };
return (
); }State with Objects:
function UserProfile() { const [user, setUser] = useState({ name: '', age: 0, email: '' });
const updateField = (field, value) => { setUser(prev => ({ ...prev, [field]: value })); };
return (
State with Arrays:
function TodoList() { const [todos, setTodos] = useState([]); const [input, setInput] = useState('');
const addTodo = () => { setTodos(prev => [...prev, { id: Date.now(), text: input }]); setInput(''); };
const removeTodo = (id) => { setTodos(prev => prev.filter(todo => todo.id !== id)); };
return (
-
{todos.map(todo => (
- {todo.text} ))}
useEffect
The useEffect hook lets you perform side effects in functional components.
Basic Side Effects:
import { useState, useEffect } from 'react';
function DocumentTitle() { const [count, setCount] = useState(0);
useEffect(() => {
document.title = Count: ${count};
}, [count]);
return ( ); }
Data Fetching:
function UserData({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null);
useEffect(() => { let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
setError(null);
}
} catch (err) {
if (!cancelled) {
setError(err.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
if (loading) return
return
Event Listeners and Cleanup:
function WindowSize() { const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
useEffect(() => { function handleResize() { setSize({ width: window.innerWidth, height: window.innerHeight }); }
window.addEventListener('resize', handleResize);
// Cleanup function
return () => {
window.removeEventListener('resize', handleResize);
};
}, []); // Empty dependency array = run once on mount
return
Timers and Intervals:
function Timer() { const [seconds, setSeconds] = useState(0); const [isRunning, setIsRunning] = useState(false);
useEffect(() => { if (!isRunning) return;
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
return () => clearInterval(interval);
}, [isRunning]);
return (
Seconds: {seconds}
useContext
The useContext hook lets you read and subscribe to context from your component.
Creating and Using Context:
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext('light');
function App() { const [theme, setTheme] = useState('light');
return (
function Toolbar() { return (
function ThemedButton() { const theme = useContext(ThemeContext);
return ( ); }
Multiple Contexts:
const ThemeContext = createContext('light'); const UserContext = createContext(null);
function App() { const [theme, setTheme] = useState('light'); const [currentUser, setCurrentUser] = useState({ name: 'John', role: 'admin' });
return (
function Dashboard() { const theme = useContext(ThemeContext); const user = useContext(UserContext);
return (
Welcome, {user.name}
Role: {user.role}
useReducer
The useReducer hook is an alternative to useState for managing complex state logic.
Basic Reducer Pattern:
import { useReducer } from 'react';
function counterReducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error(Unknown action: ${action.type});
}
}
function Counter() { const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
Count: {state.count}
Complex State Management (Task List Pattern from Context7):
function tasksReducer(tasks, action) { switch (action.type) { case 'added': { return [...tasks, { id: action.id, text: action.text, done: false }]; } case 'changed': { return tasks.map(t => { if (t.id === action.task.id) { return action.task; } else { return t; } }); } case 'deleted': { return tasks.filter(t => t.id !== action.id); } default: { throw Error('Unknown action: ' + action.type); } } }
function TaskApp() { const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
function handleAddTask(text) { dispatch({ type: 'added', id: nextId++, text: text, }); }
function handleChangeTask(task) { dispatch({ type: 'changed', task: task }); }
function handleDeleteTask(taskId) { dispatch({ type: 'deleted', id: taskId }); }
return ( <>
Prague itinerary
let nextId = 3; const initialTasks = [ { id: 0, text: 'Visit Kafka Museum', done: true }, { id: 1, text: 'Watch a puppet show', done: false }, { id: 2, text: 'Lennon Wall pic', done: false } ];
useMemo
The useMemo hook lets you cache the result of expensive calculations.
Memoizing Expensive Calculations:
import { useMemo, useState } from 'react';
function ProductList({ products, category }) { const [sortOrder, setSortOrder] = useState('asc');
const filteredAndSortedProducts = useMemo(() => { console.log('Filtering and sorting products...');
const filtered = products.filter(p => p.category === category);
return filtered.sort((a, b) => {
if (sortOrder === 'asc') {
return a.price - b.price;
}
return b.price - a.price;
});
}, [products, category, sortOrder]);
return (
-
{filteredAndSortedProducts.map(product => (
- {product.name} - ${product.price} ))}
Preventing Object Recreation:
function SearchResults({ query }) { const searchOptions = useMemo(() => ({ query, limit: 10, caseSensitive: false }), [query]);
// searchOptions object only recreated when query changes const results = useSearch(searchOptions);
return
useCallback
The useCallback hook lets you cache a function definition between re-renders.
Memoizing Event Handlers:
import { useCallback, useState } from 'react';
function ProductPage({ productId }) { const [items, setItems] = useState([]);
const handleAddToCart = useCallback(() => { setItems(prevItems => [...prevItems, productId]); }, [productId]);
return
// Memoized child component const AddToCartButton = memo(({ onAdd }) => { console.log('Button rendered'); return ; });
Optimizing Child Components:
function TodoList() { const [todos, setTodos] = useState(initialTodos);
const handleToggle = useCallback((id) => { setTodos(prevTodos => prevTodos.map(todo => todo.id === id ? { ...todo, done: !todo.done } : todo ) ); }, []);
const handleDelete = useCallback((id) => { setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id)); }, []);
return (
-
{todos.map(todo => (
useRef
The useRef hook lets you reference a value that's not needed for rendering.
Accessing DOM Elements:
import { useRef } from 'react';
function TextInput() { const inputRef = useRef(null);
function handleClick() { inputRef.current.focus(); }
return ( <> </> ); }
Storing Mutable Values:
function Stopwatch() { const [time, setTime] = useState(0); const intervalRef = useRef(null);
function handleStart() { intervalRef.current = setInterval(() => { setTime(t => t + 1); }, 10); }
function handleStop() { clearInterval(intervalRef.current); }
return (
Time: {(time / 100).toFixed(2)}s
Video Player Control (Context7 Pattern):
import { useRef, useState } from 'react';
function VideoPlayer({ src, isPlaying }) { const ref = useRef(null);
useEffect(() => { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }, [isPlaying]);
return ; }
function App() { const [isPlaying, setIsPlaying] = useState(false);
return (
<>
State Management Patterns Local State Pattern
Use local state for component-specific data that doesn't need to be shared.
function LoginForm() { const [formData, setFormData] = useState({ username: '', password: '', rememberMe: false }); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (field) => (e) => { const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value; setFormData(prev => ({ ...prev, [field]: value })); };
const validate = () => { const newErrors = {}; if (!formData.username) newErrors.username = 'Required'; if (!formData.password) newErrors.password = 'Required'; return newErrors; };
const handleSubmit = async (e) => { e.preventDefault(); const newErrors = validate();
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
setIsSubmitting(true);
try {
await login(formData);
} catch (error) {
setErrors({ submit: error.message });
} finally {
setIsSubmitting(false);
}
};
return (