React remains one of the most widely-used libraries for building interactive user interfaces. If you already know the basics, this guide will help you solidify a practical understanding of React’s core ideas: functional components, JSX, essential hooks (useState, useEffect, useContext), and how lifecycle concerns are expressed in a functional paradigm. Each section includes concise, copyable examples and links to authoritative resources.
Functional Components โ Purpose & Structure โ๏ธ
Functional components are JavaScript functions that return JSX. They are the primary unit of composition in modern React and are preferred over class components for their simplicity and compatibility with hooks.
Example: a simple counter component
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
export default Counter;
Why functional components?
- Easier to reason about as pure functions of props/state.
- Smaller surface area than class components (no
this).
JSX โ Declarative UI with JavaScript ๐จ
JSX is a syntax extension that looks like HTML but compiles to JavaScript React.createElement calls. It lets you declare UI structure and embed expressions directly inside markup.
Example: JSX basics
const name = 'Ada';
const element = <h1>Hello, {name}!</h1>;
Important JSX notes:
- Use
{}to embed JavaScript expressions. - Use
classNameinstead ofclassfor CSS classes in JSX. - JSX attributes use camelCase (
onClick,readOnly). - JSX is compiled โ browsers donโt execute it directly.
Why JSX helps:
- Keeps structure close to behavior and data (easier to reason about components).
- Enables tooling (linting, formatters, type-checking with TypeScript).
Essential Hooks โ useState, useEffect, useContext ๐ช
Hooks are functions that let functional components maintain state and side effects.
useState โ robust state management
useState returns a state variable and a setter. Use functional updates when new state depends on previous state.
const [value, setValue] = useState(initialValue);
setValue(prev => prev + 1);
Tips:
- Derive state when possible instead of storing redundant values.
- Break state into logical pieces to avoid accidental re-renders.
useEffect โ handling side effects & lifecycle equivalents
useEffect runs side effects after render. You can control when it runs with the dependency array.
useEffect(() => {
// run on mount and when `query` changes
fetchData(query).then(setData);
}, [query]);
Lifecycle equivalents using useEffect:
- Mounting:
useEffect(() => { /* init */ }, []);โ runs once after mount. - Updating:
useEffect(() => { /* react to deps */ }, [dep1, dep2]);โ runs when dependencies change. - Unmounting / cleanup: return a function from effect
useEffect(() => {
const id = subscribe(event => setState(event.data));
return () => unsubscribe(id); // cleanup on unmount
}, []);
Common pitfalls with useEffect:
- Missing dependencies in the array can cause stale closures or incorrect behavior.
- Adding non-primitive dependencies (objects/functions) may trigger extra runs โ use
useCallback/useMemoor stable refs.
useContext โ sharing global-ish state without prop drilling
useContext reads a context value created by React.createContext().
const ThemeContext = React.createContext('light');
function Toolbar() {
const theme = useContext(ThemeContext);
return <div className={`toolbar ${theme}`}>Toolbar</div>;
}
When to use Context:
- For theme, locale, auth user, or other cross-cutting concerns.
- Avoid using it as a general-purpose state store for rapidly changing data (it can trigger deep re-renders).
Handling Lifecycle with Hooks โ Patterns & Best Practices ๐
Although functional components don’t have lifecycle methods, useEffect models lifecycle patterns:
- Mount: run an effect with an empty dependency array to initialize resources.
- Update: include dependencies to react to changes.
- Unmount: return a cleanup function to free resources (timers, subscriptions).
Example: data fetching with loading and cleanup
function SearchResults({ query }) {
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
let cancelled = false;
setLoading(true);
fetch(`/api/search?q=${encodeURIComponent(query)}`)
.then(r => r.json())
.then(data => { if (!cancelled) setResults(data); })
.finally(() => { if (!cancelled) setLoading(false); });
return () => { cancelled = true; };
}, [query]);
if (loading) return <p>Loadingโฆ</p>;
return <ul>{results?.map(r => <li key={r.id}>{r.title}</li>)}</ul>;
}
This pattern avoids setting state on unmounted components and handles rapid successive queries gracefully.
Practical Patterns & Tips for Intermediate React Devs ๐งญ
- Memoization: use
useMemoanduseCallbackto avoid expensive recomputations and unnecessary re-renders, but measure before over-optimizing. - Component decomposition: components should do one thingโsplit complex UI into smaller, testable parts.
- Controlled vs uncontrolled inputs: prefer controlled inputs for predictable behavior; use uncontrolled refs for performance when needed.
- Error boundaries: hooks donโt catch render errors โ use class-based error boundaries or libraries that provide hook-friendly helpers.
- Testing: use React Testing Library for interaction-focused tests; prefer small unit-tests for pure utility logic.
Further Reading & Resources ๐
- Official React docs: react.dev
- Hooks Guide (React): react.dev - Hooks
- Dan Abramov โ “A Complete Guide to useEffect”: overreacted.io
- Kent C. Dodds โ Testing React Apps: kentcdodds.com
- CSS-Tricks: JSX overview โ CSS-Tricks: JSX
Key Takeaways & Next Steps โ
- Functional components + hooks are the modern, recommended approach in React.
useState,useEffect, anduseContextcover most everyday needs: state, side effects, and shared context.- Model lifecycle with
useEffect: mount, update via deps, and cleanup on unmount. - Favor composition, small components, and think about performance only when a problem arises.
Want me to also:
- add a runnable example app (create-react-app / Vite) with these patterns scaffolded? (I can add a
starter/directory) - add TypeScript examples for the same patterns?
If yes, tell me which you prefer and I’ll scaffold and test it locally.
Comments