Advanced React Patterns: Hooks, Context, Render Props, and Suspense

Overview

As React continues to evolve, understanding and implementing advanced patterns can set your applications apart. This post covers some key patterns that enhance your React applications: custom hooks, the Context API, render props, and React Suspense with Concurrent Mode.

Custom Hooks: Simplify and Reuse

Custom hooks allow you to encapsulate logic and reuse it across components without altering their structure. Here's a practical example:

Example: Custom Form Hook

Manage form state across multiple components using a custom hook:

import { useState } from "react";

function useForm(initialValues) {
    const [values, setValues] = useState(initialValues);

    const handleChange = (event) => {
        const { name, value } = event.target;
        setValues((prevValues) => ({
            ...prevValues,
            [name]: value,
        }));
    };

    return [values, handleChange];
}

This hook makes managing form state straightforward:

function ContactForm() {
    const [formValues, handleChange] = useForm({ name: "", email: "" });

    return (
        <form>
            <input
                type="text"
                name="name"
                value={formValues.name}
                onChange={handleChange}
            />
            <input
                type="email"
                name="email"
                value={formValues.email}
                onChange={handleChange}
            />
        </form>
    );
}

Context API: Manage Global State

The Context API is a powerful tool for managing global state without prop drilling. Here's how to use it effectively:

Example: Theme Context

Create a context to manage and provide theme information:

import React, { createContext, useState, useContext } from "react";

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
    const [theme, setTheme] = useState("light");

    return (
        <ThemeContext.Provider value={{ theme, setTheme }}>
            {children}
        </ThemeContext.Provider>
    );
}

export function useTheme() {
    return useContext(ThemeContext);
}

Access and modify the theme from any component:

function ThemeSwitcher() {
    const { theme, setTheme } = useTheme();

    return (
        <button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
            Switch to {theme === "light" ? "dark" : "light"} mode
        </button>
    );
}

Render Props: Share Logic Across Components

Render props allow you to share logic between components using a function as a child. Here's a simple example:

Example: Mouse Tracker

Track mouse position and share it with a child component:

function MouseTracker({ render }) {
    const [position, setPosition] = useState({ x: 0, y: 0 });

    const handleMouseMove = (event) => {
        setPosition({ x: event.clientX, y: event.clientY });
    };

    return <div onMouseMove={handleMouseMove}>{render(position)}</div>;
}

// Usage
<MouseTracker
    render={(position) => (
        <p>
            Mouse position: {position.x}, {position.y}
        </p>
    )}
/>;

React Suspense with Concurrent Mode: Optimize Data Fetching

React Suspense and Concurrent Mode offer a sophisticated approach to handling asynchronous operations. Here’s how to use them together for data fetching:

Example: Data Fetching with Suspense and Concurrent Mode

Fetch user data and display it using React Suspense and Concurrent Mode:

import React, { Suspense } from "react";

const UserData = React.lazy(() => {
    const userId = 1; // Example user ID
    return new Promise((resolve) => {
        fetch(`/api/user/${userId}`)
            .then((response) => response.json())
            .then((data) => {
                resolve({ default: () => <div>User: {data.name}</div> });
            });
    });
});

function App() {
    return (
        <Suspense fallback={<div>Loading user data...</div>}>
            <UserData />
        </Suspense>
    );
}

Benefits of Using Suspense and Concurrent Mode

  • Improved User Experience: Defer rendering until data is available, resulting in fewer loading states.
  • Better Performance: Prioritize important updates and maintain responsiveness during heavy data fetching.
  • Simplified Code: Reduce complexity in handling asynchronous operations.

Conclusion

Mastering these advanced React patterns can greatly enhance your development process, leading to cleaner, more maintainable, and performant applications. Implement these techniques to take your React projects to the next level.