From useState to custom hooks — performance patterns, rules, and React 19 additions.
Welcome to our deep dive into React Hooks. Over the next few slides, we'll explore how hooks revolutionized React development — from the core hooks like useState and useEffect, to performance optimization with useMemo and useCallback, custom hooks for reusable logic, and the new hooks coming in React 19. Whether you're transitioning from class components or leveling up your hooks game, this talk has something for you.
Let's start with why hooks exist. On the left, you can see a traditional class component — a constructor to initialize state, componentDidMount for side effects, and a render method. On the right, the same component rewritten with hooks. The difference is striking: hooks eliminate the boilerplate of class syntax entirely. useState replaces the constructor, useEffect replaces lifecycle methods, and the component is just a function. Looking at the comparison table below, every class lifecycle method has a hooks equivalent, but hooks compose much more naturally.
Here are the core hooks you'll use in virtually every React application. useState lets you declare reactive state variables — it returns the current value and a setter function that triggers re-renders. useEffect runs side effects after render, perfect for data fetching, subscriptions, and DOM mutations. useContext accesses context values without wrapping components in Consumer tags. useRef holds a mutable reference that persists across renders without causing re-renders — great for DOM elements and timers.
Understanding the useEffect lifecycle is crucial. As you can see in the diagram, it follows a clear flow: the component mounts, the effect runs, and when dependencies change, the cleanup function fires before the effect runs again. On unmount, cleanup fires one final time. The key insight here is that effects with an empty dependency array run only once on mount, while effects with dependencies re-run whenever those values change. An important note: always include all values from the component scope that change over time in the dependency array. Omitting dependencies is the number one source of stale closure bugs.
Custom hooks are where things get really powerful. Any function that starts with 'use' and calls other hooks is a custom hook. Looking at this code, we've extracted a useFetch hook that encapsulates loading state, error handling, and data fetching into a single reusable function. Any component can now fetch data with just one line. Running the example in the terminal, you can see how clean the component code becomes when the fetching logic is abstracted away.
Performance optimization with hooks comes down to two main tools: useMemo and useCallback. Looking at the first code example, useMemo memoizes the result of an expensive computation — it only recalculates when the dependencies change. The second example shows useCallback, which memoizes a function reference. This is critical when passing callbacks to child components that rely on referential equality to skip re-renders. The comparison table breaks it down: useMemo caches values, useCallback caches functions. Use them when you have measurable performance problems, not preemptively.
When your state logic gets complex — multiple related values, or next state depends on previous state — useReducer is your tool. Looking at this code, we define a reducer function that handles different action types: increment, decrement, and reset. The pattern should feel familiar if you've used Redux. The diagram shows the data flow: the component dispatches an action, the reducer processes it and returns new state, which triggers a re-render. This is much more predictable than multiple useState calls that depend on each other.
React hooks have two strict rules you must never break. The callout highlights the most critical one: hooks rely on call order. They must be called in the exact same order every render. This means no hooks inside conditions, loops, or nested functions. The cards below show common violations. Conditional Hook Call puts a hook inside an if statement — this changes the call order between renders, corrupting state. Hook Inside Loop means iterations vary between renders and React can't match hooks to their state. Always call hooks at the top level of your component.
React 19 introduces several exciting new hooks. useActionState manages form state with built-in pending indicators — no more manual loading states for form submissions. useOptimistic lets you show an optimistic UI update immediately while the server processes the real change. The use hook can unwrap promises and context directly, enabling cleaner async patterns. And useFormStatus gives child components access to the parent form's submission state without prop drilling.
The numbers speak for themselves. React hooks have 95% adoption among React developers. The average hook-based component uses 30% fewer lines of code compared to its class equivalent. Custom hooks see 4x more code reuse across components. And bundle sizes drop by about 15% because hooks enable better tree-shaking. Looking at the table, these aren't just vanity metrics — they translate directly into developer productivity and application performance.
Let's wrap up with the key takeaways. Start with useState and useEffect — they cover 80% of use cases. Extract custom hooks early to share logic between components. Use useMemo and useCallback only when you've measured a real performance problem. Follow the rules of hooks strictly — no conditions, no loops. And explore the new React 19 hooks to simplify forms and async patterns. Thanks for joining this deep dive into React Hooks.
Hands-on implementation guides with detailed code examples, step-by-step instructions, and expanded explanations for each topic.