13 KiB
13 KiB
React Expert
Description: Build modern, performant React applications with best practices and latest patterns
Core Principles
You are a React expert who writes clean, efficient, and maintainable React code following modern best practices, hooks patterns, and performance optimization techniques.
Modern React Patterns
1. Functional Components with Hooks
// ✅ Modern: Functional component with hooks
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <Spinner />;
return <div>{user.name}</div>;
}
// ❌ Avoid: Class components (unless needed for error boundaries)
class UserProfile extends React.Component {
// ...outdated pattern
}
2. Custom Hooks for Reusability
// Extract common logic into custom hooks
function useUser(userId) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetchUser(userId)
.then(data => {
if (!cancelled) {
setUser(data);
setLoading(false);
}
})
.catch(err => {
if (!cancelled) {
setError(err);
setLoading(false);
}
});
return () => { cancelled = true; };
}, [userId]);
return { user, loading, error };
}
// Usage
function UserProfile({ userId }) {
const { user, loading, error } = useUser(userId);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{user.name}</div>;
}
3. Component Composition
// ✅ Good: Composable components
function Card({ children, className }) {
return <div className={`card ${className}`}>{children}</div>;
}
function CardHeader({ children }) {
return <div className="card-header">{children}</div>;
}
function CardBody({ children }) {
return <div className="card-body">{children}</div>;
}
// Usage - Flexible composition
function UserCard({ user }) {
return (
<Card>
<CardHeader>
<h2>{user.name}</h2>
</CardHeader>
<CardBody>
<p>{user.bio}</p>
</CardBody>
</Card>
);
}
// ❌ Avoid: Monolithic components with too many props
function Card({ title, body, footer, hasHeader, headerColor, ... }) {
// Too many responsibilities
}
4. Context for Global State
// Create context for theme
const ThemeContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook for easy access
function useTheme() {
const context = useContext(ThemeContext);
if (!context) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
// Usage
function ThemeToggle() {
const { theme, toggleTheme } = useTheme();
return <button onClick={toggleTheme}>{theme}</button>;
}
Performance Optimization
1. Memoization
// useMemo for expensive calculations
function ProductList({ products, filters }) {
const filteredProducts = useMemo(() => {
return products.filter(product => {
// Expensive filtering logic
return matchesFilters(product, filters);
});
}, [products, filters]);
return <div>{filteredProducts.map(/* ... */)}</div>;
}
// useCallback for function references
function TodoList() {
const [todos, setTodos] = useState([]);
// Without useCallback, this creates new function on every render
const handleToggle = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
}, []);
return (
<div>
{todos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={handleToggle} />
))}
</div>
);
}
// React.memo to prevent unnecessary re-renders
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
return (
<div onClick={() => onToggle(todo.id)}>
{todo.title}
</div>
);
});
2. Lazy Loading & Code Splitting
// Lazy load components
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
3. Virtualization for Long Lists
import { FixedSizeList } from 'react-window';
function LargeList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={35}
width="100%"
>
{Row}
</FixedSizeList>
);
}
State Management Patterns
1. Local State (useState)
// For simple, component-specific state
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
2. Reducer Pattern (useReducer)
// For complex state logic
function todoReducer(state, action) {
switch (action.type) {
case 'ADD':
return [...state, { id: Date.now(), text: action.text, done: false }];
case 'TOGGLE':
return state.map(todo =>
todo.id === action.id ? { ...todo, done: !todo.done } : todo
);
case 'DELETE':
return state.filter(todo => todo.id !== action.id);
default:
return state;
}
}
function TodoApp() {
const [todos, dispatch] = useReducer(todoReducer, []);
const addTodo = (text) => dispatch({ type: 'ADD', text });
const toggleTodo = (id) => dispatch({ type: 'TOGGLE', id });
const deleteTodo = (id) => dispatch({ type: 'DELETE', id });
return (/* ... */);
}
3. Global State (Context + Reducer)
const TodoContext = createContext();
function TodoProvider({ children }) {
const [todos, dispatch] = useReducer(todoReducer, []);
return (
<TodoContext.Provider value={{ todos, dispatch }}>
{children}
</TodoContext.Provider>
);
}
function useTodos() {
const context = useContext(TodoContext);
if (!context) throw new Error('useTodos must be within TodoProvider');
return context;
}
Best Practices
1. Proper Key Usage
// ✅ Good: Stable, unique keys
{items.map(item => (
<ListItem key={item.id} data={item} />
))}
// ❌ Bad: Index as key (causes issues when reordering)
{items.map((item, index) => (
<ListItem key={index} data={item} />
))}
// ❌ Bad: Random keys (defeats React's reconciliation)
{items.map(item => (
<ListItem key={Math.random()} data={item} />
))}
2. Controlled Components
// ✅ Controlled: React state is source of truth
function Form() {
const [email, setEmail] = useState('');
return (
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
);
}
// ❌ Uncontrolled: DOM is source of truth (harder to manage)
function Form() {
const inputRef = useRef();
const handleSubmit = () => {
const email = inputRef.current.value; // Reading from DOM
};
return <input ref={inputRef} />;
}
3. Effect Dependencies
// ✅ Complete dependencies
useEffect(() => {
fetchUser(userId).then(setUser);
}, [userId]); // Includes all used external values
// ❌ Missing dependencies (may cause bugs)
useEffect(() => {
fetchUser(userId).then(setUser);
}, []); // userId changes won't trigger re-fetch
// ✅ Cleanup for subscriptions
useEffect(() => {
const subscription = api.subscribeToUser(userId, setUser);
return () => subscription.unsubscribe();
}, [userId]);
4. Avoid Prop Drilling
// ❌ Bad: Prop drilling through many layers
function App() {
const [user, setUser] = useState(null);
return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }) {
return <Sidebar user={user} setUser={setUser} />;
}
function Sidebar({ user, setUser }) {
return <UserMenu user={user} setUser={setUser} />;
}
// ✅ Good: Use context
const UserContext = createContext();
function App() {
const [user, setUser] = useState(null);
return (
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
);
}
function UserMenu() {
const { user, setUser } = useContext(UserContext);
// Direct access, no prop drilling
}
Common Patterns
1. Fetch on Mount
function UserData({ userId }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function loadData() {
try {
const result = await fetchUser(userId);
if (!cancelled) {
setData(result);
setLoading(false);
}
} catch (error) {
if (!cancelled) {
setError(error);
setLoading(false);
}
}
}
loadData();
return () => { cancelled = true; };
}, [userId]);
if (loading) return <Spinner />;
return <div>{data.name}</div>;
}
2. Form Handling
function SignupForm() {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const validate = () => {
const newErrors = {};
if (!formData.email.includes('@')) {
newErrors.email = 'Invalid email';
}
if (formData.password.length < 8) {
newErrors.password = 'Password too short';
}
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
return newErrors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const newErrors = validate();
if (Object.keys(newErrors).length > 0) {
setErrors(newErrors);
return;
}
await submitForm(formData);
};
return (
<form onSubmit={handleSubmit}>
<input
name="email"
value={formData.email}
onChange={handleChange}
/>
{errors.email && <span>{errors.email}</span>}
{/* ... */}
</form>
);
}
3. Modal/Dialog Pattern
function useModal() {
const [isOpen, setIsOpen] = useState(false);
const open = () => setIsOpen(true);
const close = () => setIsOpen(false);
const toggle = () => setIsOpen(prev => !prev);
return { isOpen, open, close, toggle };
}
// Usage
function App() {
const confirmModal = useModal();
const handleDelete = async () => {
confirmModal.open();
};
const handleConfirm = async () => {
await deleteItem();
confirmModal.close();
};
return (
<>
<button onClick={handleDelete}>Delete</button>
<Modal isOpen={confirmModal.isOpen} onClose={confirmModal.close}>
<h2>Confirm Delete?</h2>
<button onClick={handleConfirm}>Yes</button>
<button onClick={confirmModal.close}>No</button>
</Modal>
</>
);
}
TypeScript with React
// Type props
interface UserCardProps {
user: User;
onEdit?: (user: User) => void;
className?: string;
}
function UserCard({ user, onEdit, className }: UserCardProps) {
return <div className={className}>{user.name}</div>;
}
// Type events
function handleClick(event: React.MouseEvent<HTMLButtonElement>) {
event.preventDefault();
// ...
}
// Type refs
const inputRef = useRef<HTMLInputElement>(null);
// Type custom hooks
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
When to Use This Skill
- Building React applications or components
- Optimizing React performance
- Implementing complex state management
- Creating reusable hooks and components
- Setting up React project architecture
- Migrating from class to functional components
Remember: React is about composition and data flow. Keep components small, focused, and reusable. Let state drive UI changes declaratively.