The Functional Revolution
Before React 16.8, state and lifecycle methods were exclusive to Class Components. Hooks were introduced to allow functional components to “hook into” React state and lifecycle features. This led to flatter component trees and better logic reuse.
useState: Preserving Local State
The useState hook allows a component to maintain persistent data across re-renders. It returns an array with two elements: the current state value and a function to update it.
const [count, setCount] = useState(0);
When setCount is called, React schedules a re-render of the component.
useEffect: Handling Side Effects
Side effects, such as data fetching, subscriptions, or manual DOM manipulations, must be isolated from the rendering logic. useEffect runs after the component renders.
It takes two arguments:
- Effect Function: The logic to execute.
- Dependency Array: An array of values that the effect depends on. The effect only re-runs if one of these values changes.
The Cleanup Phase
If an effect creates a subscription or a timer, it must return a cleanup function to prevent memory leaks when the component unmounts or before the effect re-runs.
useRef: Persistent References
Unlike useState, changing a value in useRef does not trigger a re-render. It is primarily used for:
- Accessing DOM nodes directly.
- Storing values that need to persist for the lifetime of the component but don’t affect the UI (e.g., timer IDs).
The Rules of Hooks
To ensure that React can correctly associate internal state with functional calls, hooks must follow two strict rules:
- Only Call Hooks at the Top Level: Do not call hooks inside loops, conditions, or nested functions.
- Only Call Hooks from React Functions: Call them from React functional components or custom hooks.
Custom Hooks: Extracts and Reuses
Custom hooks allow you to extract component logic into reusable functions. This is the primary way React developers share stateful logic between different components without using complex patterns like Render Props or HOCs.
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return width;
}