Frontend Development 19 min read

Mastering React Hooks: From Basics to Advanced Patterns

This article explains why React Hooks were introduced, walks through the fundamentals of useState and useEffect with clear code examples, and demonstrates how Hooks simplify state management, side‑effects, and component logic compared to traditional class components, enabling more maintainable frontend development.

Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Tencent IMWeb Frontend Team
Mastering React Hooks: From Basics to Advanced Patterns
Transitioning from Vue to React can feel odd, especially when using React Hooks; understanding the purpose behind Hooks can help ease the shift.

1. What Are Hooks

In short, Hooks are functions that enrich functional components with additional capabilities, often providing a better alternative to class components.

1.1 Background of Hooks

Before Hooks, functional components lacked state, refs, and could only receive props, while class components suffered from several issues:

Coupled logic in complex components makes it hard to separate concerns; lifecycle methods force unrelated business logic into the same hook, requiring manual splitting.

Cleanup and resource release require manual handling in mount and unmount hooks, scattering creation and destruction logic.

Difficulty reusing logic across components despite patterns like render props or higher‑order components, leading to nested abstraction and maintenance overhead.

Steep learning curve for classes demands solid JavaScript fundamentals (this, closures, binding).

Hooks address these problems to varying degrees, and understanding their background is key to appreciating their benefits.

2. Basics of Hooks

Let's start with the simplest Hook usage.

2.1 useState

Example from the React documentation:

<code>import React, { useState } from 'react';

function Example() {
  // Declare a "count" state variable
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}</code>

The

useState

Hook adds state to a function component, allowing you to read (

count

) and update (

setCount

) it.

The Hook workflow:

When the component renders for the first time,

useState

initializes

count

with the provided value (0).

The returned VDOM displays

count

(initially 0).

Clicking the button calls

setCount

, updating

count

and re‑executing the component.

On subsequent renders,

useState

returns the latest

count

without re‑initializing.

Thus,

useState

provides simple state handling without the boilerplate of class components.

State is no longer stored in a single

state

object; multiple

useState

calls can manage independent pieces of state.

Unlike

setState

, which merges objects,

setCount

replaces the value directly.

2.2 useEffect

Before Hooks, function components couldn't access lifecycle hooks, so

useEffect

was introduced to combine mounting, updating, and cleanup logic.

2.2.1 Combining lifecycle hooks

Class component example:

<code>// Count component
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}</code>

With

useEffect

the same logic becomes:

<code>import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  // Runs after every render (mount + update)
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}</code>
useEffect

always runs after the component renders, ensuring side‑effects occur after the DOM is updated. The cleanup function returned from

useEffect

runs before the next effect or when the component unmounts, making it ideal for subscriptions, timers, or manual DOM work.

2.2.2 Cleanup (unmount) handling

When a component subscribes to external data, failing to clean up can cause memory leaks. Class components use

componentWillUnmount

; with Hooks the cleanup is returned from

useEffect

:

<code>function FriendStatus({ friend }) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }
    ChatAPI.subscribeToFriendStatus(friend.id, handleStatusChange);
    // Cleanup on unmount or when friend.id changes
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friend.id, handleStatusChange);
    };
  }, [friend.id]);

  if (isOnline === null) return 'Loading...';
  return isOnline ? 'Online' : 'Offline';
}</code>

By listing

friend.id

in the dependency array, the effect only re‑runs when the ID changes, avoiding unnecessary subscriptions.

2.2.3 Separating logic with multiple effects

Multiple

useEffect

calls can isolate concerns, similar to having several lifecycle methods but without mixing unrelated code.

2.2.4 Skipping effects

Providing a dependency array lets you skip effect execution unless listed values change, improving performance.

2.3 Beyond useEffect – useMemo

For expensive calculations,

useMemo

memoizes the result based on dependencies, eliminating the need for extra state and re‑renders:

<code>const Chart = ({ dateRange }) => {
  const data = useMemo(() => getDataWithinRange(dateRange), [dateRange]);
  return <svg className="Chart" />;
};</code>

If the computation is cheap, you can even call it directly inside the component without memoization.

2.4 Handling multiple data dependencies

Complex components often need to recompute values when several inputs change. With Hooks you can express each dependency clearly:

<code>const Chart = ({ dateRange, margins }) => {
  const data = useMemo(() => getDataWithinRange(dateRange), [dateRange]);
  const dimensions = useMemo(getDimensions, [margins]);
  const xScale = useMemo(getXScale, [data, dimensions]);
  const yScale = useMemo(getYScale, [data, dimensions]);
  return <svg className="Chart" />;
};</code>

This eliminates the verbose

if

checks and multiple

setState

calls required in class components, making the component logic concise and easier to read.

3. The Real Power of Hooks

Hooks shift the mental model from class‑based lifecycle management to a function‑centric approach where you focus on the values that need to stay in sync.

Examples show that

useEffect

is more than a lifecycle replacement; it handles side‑effects, subscriptions, and cleanup in a unified way.

useMemo

removes unnecessary state and re‑renders, and multiple effects let you separate concerns without tangled code.

4. References

React Official Documentation

Thinking in React Hooks

frontendJavaScriptReactHooksuseEffectuseState
Tencent IMWeb Frontend Team
Written by

Tencent IMWeb Frontend Team

IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.