Skip to content

Chapter 7 of 10

Advanced State Management with Context API

This chapter covers the Context API for state management, discussing patterns and best practices for using Context effectively in larger applications.

From React Core Engineer by Deepak Kumar · 595 words · free to read

Introduction to Hooks

React Hooks have fundamentally changed how developers build components, allowing for stateful logic and side effects to be integrated without using class components. The introduction of Hooks was a pivotal shift in the React ecosystem, enabling functional components to have state and lifecycle features previously exclusive to class components. Understanding how Hooks operate under the hood is crucial for mastering React's modern paradigm.

Hooks are functions that let you "hook into" React state and lifecycle features from function components. They are executed in a specific order during a component's lifecycle, aligning with React's rendering and reconciliation processes. This chapter will dissect the internal workings of Hooks, providing insights into how they fit into the broader React lifecycle.

Rules of Hooks

Hooks must adhere to a strict set of rules to ensure consistent behavior across component updates and renders. These rules are not mere guidelines; they are enforced by React to maintain proper state management and lifecycle integration. Let's examine these rules more closely:

Only Call Hooks at the Top Level

Hooks should be called at the top level of a React function component. This ensures that Hooks are executed in the same order on every render. Conditional or nested Hook calls can lead to unpredictable behavior because the order of execution may change. Consider the following example:

import React, { useState, useEffect } from 'react';

function ExampleComponent() {
    const [count, setCount] = useState(0);

    // Correct: Hook is called at the top level
    useEffect(() => {
        document.title = `Count: ${count}`;
    }, [count]);

    // Incorrect: Hook inside a conditional
    if (count > 0) {
        const [state, setState] = useState('Some state'); // This will cause issues
    }

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

The use of useState inside the conditional block will break the Rules of Hooks, potentially leading to inconsistent state.

Only Call Hooks from React Functions

Hooks should only be called from React function components or custom Hooks. This ensures that all Hooks are part of the React component lifecycle. Calling Hooks outside of this context will not work, as they rely on React's internal state tracking.

import React, { useState } from 'react';

// Correct: Hook called inside a React function component
function MyComponent() {
    const [value, setValue] = useState(0);
    return <div>{value}</div>;
}

// Incorrect: Hook called outside of a React function
const value = useState(0); // This will throw an error

Adhering to these rules ensures that Hooks integrate seamlessly with React's rendering and reconciliation mechanisms, maintaining consistent behavior across component updates.

Common Hook Patterns

Hooks offer flexibility and composability, allowing developers to create custom Hooks and reuse logic across components. Let's explore some common patterns that leverage Hooks effectively.

Custom Hooks for Reusable Logic

Custom Hooks are a way to extract component logic into reusable functions. They follow the naming convention use* and can encapsulate stateful logic that can be reused across different components. Here's an example of a custom Hook for managing form inputs:

import { useState } from 'react';

function useFormInput(initialValue: string) {
    const [value, setValue] = useState(initialValue);

    function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
        setValue(event.target.value);
    }

    return {
        value,
        onChange: handleChange
    };
}

function MyFormComponent() {
    const name = useFormInput('John Doe');

    return (
        <div>
            <input type="text" {...name} />
            <p>Hello, {name.value}!</p>
        </div>
    );
}

This pattern provides a clean way to share form input logic across multiple components without duplicating code.

Effect Hook for Side Effects

The useEffect Hook is used for executing side effects in function components. Side effects can include data fetching, subscriptions, or manually changing the DOM. The useEffect Hook accepts a dependency array that determines when the effect should re-run.

import React, { useState, useEffect } from 'react';

function FetchDataComponent() {
    const [data, setData] = useState<any>(null);

    useEffect(() => {
        async function fetchData() {
            const response = await fetch('https://api.example.com/data');
            const json = await response.json();
            setData(json);
        }

        fetchData();
    }, []); // Empty dependency array means this effect runs once on mount

    return (
        <div>
            {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'}
        </div>
    );
}

Correctly managing dependencies ensures that side effects are executed at the appropriate times, avoiding unnecessary re-renders or stale data.

Memoization with useMemo and useCallback

To optimize performance, useMemo and useCallback Hooks can be used to memoize expensive calculations and functions. These Hooks help prevent unnecessary re-computations and prop changes, thus reducing re-renders.

Here's how useMemo can be used to memoize a computed value:

import React, { useState, useMemo } from 'react';

function ExpensiveCalculationComponent({ numbers }: { numbers: number[] }) {
    const [multiplier, setMultiplier] = useState(1);

    const total = useMemo(() => {
        console.log('Calculating total...');
        return numbers.reduce((acc, num) => acc + num * multiplier, 0);
    }, [numbers, multiplier]);

    return (
        <div>
            <p>Total: {total}</p>
            <button onClick={() => setMultiplier(multiplier + 1)}>
                Increase Multiplier
            </button>
        </div>
    );
}

By using useMemo, the calculation is only recomputed when numbers or multiplier changes, optimizing the component's rendering.

Conclusion

Understanding the internals of React Hooks and the Rules of Hooks is fundamental for building efficient React applications. Hooks provide a powerful way to manage state and lifecycle events in function components, supporting the development of modular and reusable code. The subsequent chapter will build on this knowledge, exploring modern React patterns such as Server Components and concurrent rendering. These concepts will further enhance your ability to design and implement complex React applications.

All chapters

  1. 1
  2. 3
  3. 4
  4. 6
  5. 7
    Advanced State Management with Context API
  6. 8
  7. 9
  8. 10
  9. 11
  10. 12
Message me