React Hooks

Simplify your React code and write more intuitive components with powerful Hooks. Manage application state, handle side effects, and implement component logic in a cleaner and more streamlined way. React Hooks empower you to leverage functional components to their full potential, keeping your codebase organized and maintainable.

React-Hooks
Table of Contents

Understanding Hooks

React Hooks give functional components the power to manage their state and interact with React’s lifecycle features. These special functions, always starting with “use”, simplify how you write components. Think of Hooks as tools to “hook into” a functional component to add dynamic behavior and handle state changes. Understanding Hooks allows you to write cleaner, more organized code while avoiding the complexities associated with class-based components. With Hooks, you can focus on the logic of your components and create reusable functionality for your React applications.

React Hooks

Before React Hooks (introduced in React 16.8), managing state and side effects in React primarily relied on class components. React Hooks introduced a paradigm shift in React development. They are special functions that always start with the keyword use and provide access to various React features like state management and lifecycle methods directly within functional components. This eliminates the need for class components in many scenarios, leading to cleaner, more concise, and reusable components.

Example Demonstrating a Hook for State Management

import React, { useState } from 'react';

function ToggleSwitch() {
    const [isToggled, setIsToggled] = useState(false);

    const handleToggle = () => {
        setIsToggled(!isToggled);
    };

    return (
        <div>
            <button onClick={handleToggle}>
                {isToggled ? 'ON' : 'OFF'}
            </button>
        </div>
    );
}

export default ToggleSwitch;

Explanation

  • Line 1: Imports the React library and the useState hook from it, which is used for state management in functional components.
  • Line 3: Declares a functional component named ToggleSwitch.
  • Line 4: Initializes a state variable isToggled with the default value of false, and a function setIsToggled to update it. This state represents whether the toggle switch is on or off.
  • Line 6-8: Defines a function handleToggle that toggles the state of isToggled between true and false each time it’s called.
  • Line 10-16: Renders a button that displays 'ON' if isToggled is true and 'OFF' if it’s false. The button’s onClick event is linked to the handleToggle function, toggling its state when clicked.

Here, the useState Hook provides a way to manage state within the functional component ToggleSwitch, simplifying component logic and improving readability. This is just one example of the various Hooks available in React, each offering functionalities that empower functional components.

Hook Rules

React Hooks offer a powerful way to manage state and side effects in functional components. However, to ensure proper functionality and avoid errors, following a few key rules regarding when and where you can call Hooks is essential.

  • Call Hooks at the Top Level
    Hooks rely on React’s rendering order to function correctly. Calling them at the top level of your component (outside of conditional statements or loops) guarantees this order is preserved across renders.
  • Call Hooks Within React Functions
    Hooks can only be called within functional components or custom Hooks (functions that themselves use Hooks). This ensures they can access React’s context and interact with the component lifecycle.

Example of Incorrect Hook Usage

import React, { useState } from 'react';

function Counter() {
    let condition = true; // Imagine some logic setting this condition
    
    if (condition) {
        const [count, setCount] = useState(0);  // Incorrect Hook placement
    }

    const handleIncrement = () => {
        setCount(count + 1); // Error: ReferenceError (setCount is not defined)
    };

    return (
        <div>
            <p>Count: {count}</p>  // Error: ReferenceError (count is not defined)
            <button onClick={handleIncrement}>Increment</button>
        </div>
    );
}

export default Counter;

Explanation

  • Lines 3-4: Define a functional component Counter.
  • Lines 6-8: This conditional placement of the useState Hook is incorrect. Hooks need to be called at the top level.
  • Lines 10-12: The handleIncrement function attempts to use setCount but will throw an error because it’s not defined within this scope (conditionally created Hook).
  • Lines 15-16: Similar error for count. It’s not accessible outside the conditional block.

Example of Correct Hook Usage

import React, { useState } from 'react';

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

    const handleIncrement = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={handleIncrement}>Increment</button>
        </div>
    );
}

export default Counter;

Explanation

  • Line 4: Uses the useState hook to declare a state variable count with an initial value of 0 and setCount for updating count.
  • Line 6-8: Defines handleIncrement, which increments the count state by one.
  • Line 10-15: Renders a div containing a paragraph to display the count and a button that calls handleIncrement when clicked to increase the count.

Following these Hook rules ensures your components function as expected, avoid unexpected errors, and keep your code maintainable. Remember, Hooks should always be called at the top level and within React functions or custom Hooks.


Essential Built-in Hooks

React’s core functionality is enhanced by a collection of built-in Hooks offering powerful tools for state management, side effects, and more. The essential useState Hook enables you to manage state within functional components, making them reactive to user interactions. The versatile useEffect Hook allows you to handle actions beyond the initial UI render, such as data fetching or subscriptions. For sharing global data, the useContext Hook eliminates tedious prop drilling, streamlining data flow in complex applications. These built-in Hooks form the backbone of modern React development, providing robust solutions to common problems.

useState: State Management in Functional Components

Before React Hooks, managing state in React primarily involved class components. The useState Hook revolutionized this by allowing you to manage state directly within functional components. It provides a way to store and update data within a component, enabling dynamic behavior and UI updates based on state changes.

Key Functionalities Offered By useState

  • Initialization: You can define the initial state value during component creation using useState.
  • Reading Values: The current state value is readily accessible within the component using the state variable.
  • Updating State: A setter function is provided to update the state value, triggering a re-render of the component with the new state.

Example: TextInput Component with useState

import React, { useState } from 'react';

function TextInput() {
    const [text, setText] = useState('');

    const handleChange = (event) => {
        setText(event.target.value);
    };

    return (
        <div>
            <input type="text" value={text} onChange={handleChange} />
            <p>You typed: {text}</p>
        </div>
    );
}

export default TextInput;

Explanation

  • Line 3: Declares a functional component named TextInput.
  • Line 4: Initializes a state variable named text with an empty string as its initial value and setText as the function to update this state. This state stores the current value of the input field.
  • Lines 6-8: Defines a function handleChange that updates the text state with the input field’s current value whenever the input changes.
  • Line 10-15: Renders an input field and a paragraph. The input field displays the current value of text and updates it with each change. The paragraph dynamically displays what the user has typed.

In this example, useState effectively manages the text state within the functional component TextInput. The initial state is set, the current value is accessed, and the state is updated using the provided setter function. This demonstrates how useState simplifies state management in functional React components, allowing you to work with objects and arrays as state values.

useEffect: Managing Side Effects in React

The useEffect Hook is another essential tool in the React Hooks arsenal. It allows you to perform side effects within functional components. Side effects are actions beyond rendering the UI, such as data fetching, subscriptions, timers, or DOM manipulation.

What useEffect Offers

  • Executing Side Effects
    You can define a function within useEffect that encapsulates your side effect logic. This function executes after the component renders.
  • Cleanup (Optional)
    A cleanup function can be returned from useEffect to perform actions when the component unmounts or before the next effect runs (if dependencies change). This is crucial for preventing memory leaks or unintended side effects.

Fetching Data on Component Mount Example

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

function DataFetcher() {
    const [data, setData] = useState(null);

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

        fetchData();
        // Cleanup function (optional)
        return () => {
            // Logic for cleanup (e.g., unsubscribe from subscriptions)
        };

    }, []); // Empty dependency array: run only on mount

    if (data === null) {
        return <p>Loading data...</p>;
    }

    return (
        <div>
            <h1>{data.title}</h1>
            <p>{data.content}</p>
        </div>
    );
}

export default DataFetcher;

Explanation

  • Line 1: Imports React, and two hooks (useState and useEffect) from the React library.
  • Line 3: Declares a functional component named DataFetcher.
  • Line 4: Uses the useState hook to declare a state variable data with an initial null value. This state will store the fetched data.
  • Line 6-19: Utilizes the useEffect hook to perform side effects. In this case, it fetches data from an API.
    • Line 7-11: Defines an asynchronous function fetchData that fetches data from the given URL and updates the data state with the fetched data.
    • Line 13: Calls fetchData within the useEffect to execute it when the component mounts.
    • Line 15-17: Optionally includes a cleanup function that executes when the component unmounts or before the effect runs again.
    • Line 19: The empty dependency array [ ] tells React to run the effect only once after the initial render.
  • Line 21-23: Conditionally renders a paragraph element displaying "Loading data..." if data is null.
  • Line 25-30: JSX to display fetched data (title and content). if data is not null.

This example demonstrates how useEffect fetches data on component mount. The optional cleanup function highlights the importance of handling cleanup tasks when necessary. Using useEffect effectively, you can manage side effects in your React applications while maintaining a clean and organized codebase.

Sharing Data with useContext

Managing data flow in complex React applications can become cumbersome with traditional prop drilling. This involves passing data down through multiple component levels, making your code harder to maintain. Here’s where useContext comes in.

useContext allows you to share data across components in a React application without explicitly passing props down the component hierarchy. It provides a mechanism to create a context object that stores the data and allows child components to access it.

Sharing Data Example 1

import React, { createContext, useContext } from 'react';

const UserContext = createContext();

function UserProvider({ children }) {
    const user = { name: 'Alice', age: 30 };
    return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
}

function UserProfile() {
    const user = useContext(UserContext);
    return (
        <div>
            <p>Name: {user.name}</p>
            <p>Age: {user.age}</p>
        </div>
    );
}

function App() {
    return (
        <UserProvider>
            <UserProfile />
        </UserProvider>
    );
}

Explanation

  • Line 1: Imports the necessary React functions.
  • Line 3: Creates a UserContext to hold the user data.
  • Line 5-7: Defines UserProvider, a component that provides user data to its child components.
    • Line 6: Sets the user object with name and age.
    • Line 7: Uses UserContext.Provider to pass the user object down the component tree.
  • Line 10-18: Creates UserProfile, a component that consumes the user data using useContext.
    • Line 11: Uses useContext(UserContext) to access the user data provided by UserProvider.
    • Line 14-15: Displays the user’s name and age.
  • Line 20-25: Sets up the App component, which uses UserProvider to wrap the UserProfile component, demonstrating how to provide and consume context in a React app.
    • Line 22-24: Renders UserProfile inside UserProvider, ensuring UserProfile has access to the context provided by UserProvider.

Sharing Theme Preferences Example 2

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

const ThemeContext = createContext({
    theme: 'light',
    toggleTheme: () => { },
});

function ThemeProvider({ children }) {
    const [theme, setTheme] = useState('light');
    const toggleTheme = () => {
        setTheme(theme === 'light' ? 'dark' : 'light');
    };

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

function Header() {
    const { theme, toggleTheme } = useContext(ThemeContext);

    return (
        <header style={{ backgroundColor: theme === 'light' ? 'white' : 'black' }}>
            <h1>My App</h1>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </header>
    );
}

function App() {
    return (
        <ThemeProvider>
            <Header />
            {/* Other components that can access theme context */}
        </ThemeProvider>
    );
}

export default App;

Explanation

  • Line 3-6: Creates a ThemeContext using React’s createContext to hold and manage the theme state ('light' or 'dark') and a function to toggle between these themes.
  • Line 8-19: Defines ThemeProvider, a component that uses React’s useState hook to manage the theme state and provide the current theme and a toggle function to its children through ThemeContext.Provider.
    • Line 9: Initializes the theme state with a default value of 'light'.
    • Line 10-11: toggleTheme is a function that toggles the theme state between 'light' and 'dark'.
    • Line 15-17: Wraps the children prop in ThemeContext.Provider to pass down the theme and toggleTheme to the component tree.
  • Line 21-30:Header component consumes the theme and toggleTheme from ThemeContext using the useContext hook. It displays the header, which changes style based on the current theme and a button to toggle it.
    • Line 22: Extracts theme and toggleTheme from ThemeContext to use in this component.
    • Line 25: Applies conditional styling to the header based on the current theme value.
    • Line 27: Button that calls toggleTheme when clicked, changing the theme.
  • Line 32-39: App component renders the ThemeProvider and Header, indicating the usage of the context in a typical React application structure. This setup ensures that any component within the ThemeProvider can access and modify the theme.

Additional Key Hooks

React’s Hook collection extends beyond the core essentials, offering even more specialized tools for refined control and optimization. The useMemo Hook helps you avoid redundant calculations by memoizing values, ensuring computations happen only when necessary. For working with callbacks that should remain stable across re-renders, useCallback prevents unnecessary child component updates. The useLayoutEffect Hook is similar to useEffect but specifically tuned for updates impacting the DOM’s layout, allowing you to synchronize visual changes precisely. Mastering these advanced Hooks empowers you to create even more efficient and performant React applications.

useRef: Interacting with DOM and Preserving Values

React Hooks offer various functionalities, and useRef serves a unique purpose. It allows you to directly access DOM elements within functional components or store mutable values that don’t trigger re-renders when updated. This is useful in scenarios where you must interact with the DOM imperatively or manage values that don’t directly affect the UI.

How to Use useRef to Access a DOM Element Example

import React, { useRef } from 'react';

function InputFocus() {
    const inputRef = useRef(null);

    const focusInput = () => {
        if (inputRef.current) {
            inputRef.current.focus();
        }
    };

    return (
        <div>
            <input ref={inputRef} type="text" />
            <button onClick={focusInput}>Focus Input</button>
        </div>
    );
}

export default InputFocus;

Explanation

  • Line 1: Imports React and the useRef hook from the ‘react’ package.
  • Line 3-18: Defines the InputFocus functional component, which renders an input field and a button that focuses on the input field when clicked.
    • Line 4: Initializes a ref called inputRef with null. This ref will be attached to an input element, allowing direct access to the DOM element.
    • Line 6-10: Declares a function focusInput that focuses on the input field if inputRef.current is not null. This demonstrates checking if the ref is attached to an element before calling a method.
    • Line 14: Uses the ref attribute to attach inputRef to the input element. This is how React links the ref created in the JavaScript code to a DOM element.
    • Line 15: The button element has an onClick event listener that calls the focusInput function when clicking the button, demonstrating interaction and DOM manipulation using refs in React.
  • Line 20: Exports the InputFocus component for other application parts.

In this example, useRef creates a ref (inputRef) that holds a reference to the input element when it’s rendered. The focusInput function can directly access and manipulate the DOM element using inputRef.current.focus(). This demonstrates how useRef allows interaction with DOM elements without causing unnecessary re-renders, keeping your component performant.

useReducer: Handling Complex State with Ease

While useState is a powerful tool for managing state in React components, it can become cumbersome when dealing with complex state updates or interactions between multiple state variables. This is where useReducer comes in.

useReducer offers an alternative approach to state management inspired by Redux. It provides a way to manage state using a reducer function. This function takes the current state and an action object as arguments and returns the updated state. This approach promotes cleaner logic and easier handling of complex state updates.

Example of useReducer for Managing a Counter with Increment/Decrement Functionality

import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + 1 };
        case 'decrement':
            return { count: state.count - 1 };
        default:
            return state;
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);

    const handleIncrement = () => {
        dispatch({ type: 'increment' });
    };

    const handleDecrement = () => {
        dispatch({ type: 'decrement' });
    };

    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={handleIncrement}>Increment</button>
            <button onClick={handleDecrement}>Decrement</button>
        </div>
    );
}

export default Counter;

Explanation

  • Line 1: Imports React and the useReducer hook from the React library.
  • Line 3: Defines the counter’s initial state as an object with a count property set to 0.
  • Line 5-14: Declares the reducer function, which determines how state changes in response to actions sent to it.
    • Line 6-13: Inside the reducer, a switch statement handles different action types: increment and decrement, updating the state accordingly.
  • Line 16-34: Defines the Counter functional component that uses the useReducer hook to manage state based on actions.
    • Line 17: Initializes state and dispatch using the useReducer hook, with reducer as the reducer function and initialState as the initial state.
    • Line 19-21: handleIncrement is a function that dispatches an increment action when called.
    • Line 23-25: handleDecrement is a function that dispatches a decrement action when called.
    • Line 27-33: Renders the component UI, including displaying the current count and buttons to increment or decrement the count.
  • Line 36: Exports the Counter component to be used in other application parts.

In this example, useReducer provides a centralized way to manage the counter state through the reducer function. Dispatching actions with specific types allows for clear and predictable state updates, making it a valuable tool for handling complex state logic in React components.


Optimization Hooks

React’s optimization hooks offer tools to enhance your application’s efficiency. useCallback helps prevent unnecessary re-renders of child components by memoizing callback functions, ensuring their referential equality remains intact unless dependencies change. For computationally intensive operations, useMemo provides relief by caching the results of such calculations. This prevents redundant computations from happening, keeping your components performant and responsive. Expertly utilizing these optimization hooks allows you to streamline your React components, resulting in a faster and more seamless user experience.

useCallback: Optimizing Child Component Renders with Memoized Functions

React’s useCallback Hook is crucial in optimizing performance, specifically when dealing with child components. It allows you to memoize functions within functional components. Memoization means caching a function’s return value based on its arguments. This prevents unnecessary re-renders of child components that rely on these functions, even if the parent component re-renders.

Example of useCallback to Prevent a Child Component From Re-Rendering Unnecessarily

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

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

    const calculateSomething = useCallback((num) => {
        for (let i = 0; i < 1000000; i++) { } // Simulate complex calculation
        return num * 2;
    }, [count]);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <ChildComponent value={count} calculate={calculateSomething} />
        </div>
    );
}

function ChildComponent({ value, calculate }) {
    const result = calculate(value);

    return (
        <p>Calculated result: {result}</p>
    );
}

export default ParentComponent;

Explanation

  • Line 1: Imports the React library and the useState and useCallback hooks.
  • Line 3-18: Defines ParentComponent, a component that maintains a count state and provides a way to increment it.
    • Line 4: Initializes the count state variable with a default value of 0.
    • Line 6-9: Defines a memoized function calculateSomething that does a complex calculation. The useCallback hook memoizes it, so it only recalculates when count changes.
      • Line 7: A for-loop simulates an expensive computation that doesn’t change the state.
      • Line 8: Returns double the input number.
    • Line 13: Renders the current count value.
    • Line 14: Provides a button to increment the count.
    • Line 15: Renders ChildComponent, passing the count and the memoized calculateSomething function as props.
  • Line 20-26: Defines ChildComponent, which receives value and a calculate function as props.
    • Line 21: Calls the calculate function (memoized version) with the received value.
    • Line 23-25: Renders the calculated result.

In this example, the calculateSomething function is computationally expensive. Without memoization, even a slight change in count would trigger a re-render of the ChildComponent because it receives a new function reference on each render. However, useCallback ensures that the memoizedCalculate function only gets recreated when count changes, preventing unnecessary re-renders in the ChildComponent and improving overall performance.

useMemo: Optimizing Performance with Memoized Computations

React’s useMemo Hook is a valuable tool for enhancing application performance. It allows you to memoize the results of expensive computations within functional components. Memoization means caching a function’s return value based on its arguments. Using useMemo, you can prevent these computations from being re-executed unnecessarily on every render, improving your application’s overall responsiveness.

Example of useMemo to Memoize a Complex Calculation

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

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

    const calculateSomething = (num) => {
        for (let i = 0; i < 1000000; i++) { } // Simulate complex calculation
        return num * 2;
    };

    const memoizedValue = useMemo(() => calculateSomething(count), [count]);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <p>Calculated result: {memoizedValue}</p>
        </div>
    );
}

export default ExpensiveComponent;

Explanation

  • Line 1: Imports React and its hooks useState and useMemo.
  • Line 3-20: Defines ExpensiveComponent, a component that maintains a count state and demonstrates an expensive calculation.
    • Line 4: Initializes the count state variable with a default value of 0.
    • Line 6-9: Defines calculateSomething, a function to simulate an expensive calculation, such as processing large datasets or complex algorithms. The function multiplies its input by 2 after a loop that simulates complexity.
      • Line 7: This loop simulates an expensive task by running a no-operation loop 1,000,000 times. It’s purely for simulation and has no real effect other than delaying the function.
    • Line 11: Uses the useMemo hook to memoize the result of calculateSomething(count). This optimizes performance by recalculating only when count changes instead of recalculating on every render.
    • Line 15-17: Renders the current count, a button to increment count, and displays the memoized calculation result. This demonstrates how to trigger a recalculation and show memoized data in the UI.
  • Line 22: Exports ExpensiveComponent to allow it to be used elsewhere in the application.

In this example, calculateSomething is computationally expensive. Every render would trigger a new calculation without memoization, potentially impacting performance. However, useMemo ensures the calculation only happens when count changes, significantly improving performance by preventing unnecessary re-computations. This approach is beneficial for any expensive computations within your components, boosting the overall responsiveness of your React application.


Creating Custom Hooks

Building custom hooks in React is like creating your specialized toolset. Just as hooks like useState and useEffect streamline common tasks within components, custom hooks let you encapsulate reusable logic that involves both state and side effects. When building these hooks, you start by defining a regular function that internally employs other hooks. This function intelligently manages state, handles side effects like data fetching, then returns the essential values and functions for a component to use. You achieve cleaner code, better organization, and streamlined development across your React projects by crafting your custom hooks.

Organizing and Reusing Functionality with Custom Hooks

React Hooks offer a powerful paradigm for managing state and side effects within functional components. But as your application grows, managing complex logic within individual components can become cumbersome. This is where custom Hooks come in.

Custom Hooks allow you to encapsulate reusable logic that involves state and side effects. They function as custom React functions that can leverage other Hooks internally. This promotes code organization, reusability, and better separation of concerns.

Custom Hook for Fetching Data Example

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

function useFetchData(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        const fetchData = async () => {
            try {
                const response = await fetch(url);
                const fetchedData = await response.json();
                setData(fetchedData);
            } catch (error) {
                setError(error);
            } finally {
                setLoading(false);
            }
        };
        fetchData();
    }, [url]); // Dependency: url
    return { data, loading, error };
}

function MyComponent() {
    const { data, loading, error } = useFetchData('https://api.example.com/data');
    if (loading) return <p>Loading data...</p>;
    if (error) return <p>Error: {error.message}</p>;

    return (
        <div>
            <h1>{data.title}</h1>
            <p>{data.content}</p>
        </div>
    );
}

export default MyComponent;

Explanation

  • Line 1: Import React, useState, and useEffect for state management and side effects.
  • Line 3: Defines a custom hook useFetchData that fetches data from a URL.
    • Line 4-6: Initializes state variables data, loading, and error with default values.
    • Line 8-21: Uses useEffect to perform the side effect of fetching data from the URL. The effect runs when the url dependency changes.
      • Line 9-19: Defines an asynchronous function fetchData inside the effect to fetch data from the internet.
        • Line 10-18: Tries to fetch data from the URL, parse it as JSON, and update the state accordingly. Catches and sets any error that occurs during the fetch operation. Finally, sets loading to false once the operation is complete.
  • Lines 25-36: Defines a React component MyComponent that uses the useFetchData hook to fetch data.
    • Line 26: Destructures the data, loading, and error from the custom hook call useFetchData.
    • Line 27-28: Conditionally renders messages based on the loading and error states.
    • Line 30-35: Renders the fetched data (title and content) when successful.
  • Line 38: Exports the MyComponent.

In this example, the useFetchData custom Hook encapsulates the logic for fetching data, managing loading states, and handling errors. This reusable Hook can be used in various components that must fetch data, promoting code organization and reducing redundancy. You can create custom Hooks for various functionalities, improving the maintainability and reusability of your React applications.

Custom Hook for Local Storage

Custom Hooks are a powerful way to encapsulate reusable logic in React applications. Here’s a step-by-step example of building a simple custom Hook to manage data in local storage:

1. Import Hooks and Define the Hook Function

import React, { useState } from 'react';

function useLocalStorage(key, initialValue) {
    // ... (rest of the code)
}

Explanation

  • Line 1: Imports useState for state management.
  • Line 3: Defines a custom Hook named useLocalStorage that takes two arguments:
    • key: A string representing the key for the local storage item.
    • initialValue: The initial value to use if no data exists for the key.

2. Retrieve Initial Value from Local Storage

const [storedValue, setStoredValue] = useState(() => {
    try {
        const item = window.localStorage.getItem(key);
        return item ? JSON.parse(item) : initialValue;
    } catch (error) {
        console.error(error);
        return initialValue;
    }
});

Explanation

  • Line 4-12: Initializes a state variable storedValue with a function that tries to get the current value from local storage using the provided key. If the key exists, it parses the stored string back into its original format and returns it; otherwise, it returns the initialValue.
    • Line 5-11: Tries to retrieve and parse the item from local storage. If the item does not exist or an error occurs (like if JSON parsing fails), it catches the error, logs it, and returns the initialValue.

3. Update Data and Local Storage

const setValue = (value) => {
    try {
        const valueToStore =
            value instanceof Function ? value(storedValue) : value;
        setStoredValue(valueToStore);
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
        console.error(error);
    }
};

Explanation

  • Line 14-23: Defines a function setValue that allows updating the value in local storage and the component’s state. This function accepts a new value or a function that updates based on the current value.
    • Line 15-22: Tries to determine the value to be stored, considering whether the new value is a function that needs the current value to calculate the next value. It updates the component’s state and synchronously updates local storage. If an error occurs during this process (for example, if local storage is full), it catches the error and logs it.

4. Return State and Update Function

return [storedValue, setValue];

Complete Code

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

function useLocalStorage(key, initialValue) {
    const [storedValue, setStoredValue] = useState(() => {
        try {
            const item = window.localStorage.getItem(key);
            return item ? JSON.parse(item) : initialValue;
        } catch (error) {
            console.error(error);
            return initialValue;
        }
    });

    const setValue = (value) => {
        try {
            const valueToStore =
                value instanceof Function ? value(storedValue) : value;
            setStoredValue(valueToStore);
            window.localStorage.setItem(key, JSON.stringify(valueToStore));
        } catch (error) {
            console.error(error);
        }
    };

    return [storedValue, setValue];
}

This custom Hook provides a clean and reusable way to manage data in local storage across React components. It retrieves the initial value, updates the state, and synchronizes the data with local storage, simplifying data persistence logic in your application.


New Hooks in React 18 (useId, useTransition)

React 18 introduces a set of new hooks to manage specific tasks and improve component behavior. Here, we’ll explore two of these hooks and show their practical applications:

1. useId Hook: Generating Unique Identifiers

The useId hook provides a convenient way to generate unique identifiers within your React components. These IDs can be helpful for accessibility purposes, form element labels, or other scenarios requiring a unique and consistent ID.

Example

import React from 'react';
import { useId } from 'react';

function MyComponent() {
    const uniqueId = useId(); // Generate a unique ID

    return (
        <>
            <label htmlFor={uniqueId}>Enter your name:</label>
            <input type="text" id={uniqueId} />
        </>
    );
}

Explanation

  • Line 1 & 2: This imports React and the useId hook from the React library. useId generates a unique ID that can be used to associate a label with an input element for accessibility purposes.
  • Line 5: Inside MyComponent, useId is called to generate a unique ID and store it in the variable uniqueId. This unique ID will associate the label with the corresponding input field.
  • Line 8-11: Inside the return statement, a fragment wraps a label and an input element. The label element uses htmlFor to reference the ID of the input element, ensuring the label is associated with the input. This association is made using the unique ID generated by useId, which is applied to both the label’s htmlFor attribute and the input’s id attribute.

This example showcases how useId simplifies creating unique IDs within your components, promoting better accessibility and maintainability.

2. useTransition Hook: Prioritizing Updates

The useTransition hook and the startTransition API introduce a way to mark updates as urgent or non-urgent. This allows React to prioritize rendering critical updates while delaying less crucial ones, leading to a smoother user experience.

Example

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

function MyComponent() {
    const [data, setData] = useState(null);
    const [isPending, startTransition] = useTransition();

    const handleClick = () => {
        startTransition(async () => {
            setIsPending(true);
            // Simulate data fetching (replace with actual logic)
            await new Promise((resolve) => setTimeout(resolve, 1000));
            setData('Data Loaded!');
            setIsPending(false);
        });
    }

    return (
        <div>
            <button onClick={handleClick}>Fetch Data</button>
            {isPending && <p>Loading...</p>}
            {data && <p>Data: {data}</p>}
        </div>
    );
}

Explanation

  • Line 1 & 2: Imports the useState and useTransition hooks from React. These hooks are used for managing state and handling state transitions.
  • Line 5-6: Initializes state variables data for storing fetched data and uses useTransition for managing transitions. useTransition returns an array where the first element indicates if a transition is pending, and the second is a function to start the transition.
  • Line 8-16: Defines the handleClick function triggered on button click.
    • Line 9-15: Inside startTransition, an asynchronous callback is provided. This is where data fetching logic is simulated with a timeout. setIsPending is used to control the rendering of the loading state before and after the data fetch operation.
  • Line 18-24: The component’s return statement. It conditionally renders a loading message and the fetched data based on the isPending and data state variables.
    • Line 20: A button that, when clicked, calls handleClick to start the data fetching process.
    • Line 21-22: Conditional rendering based on isPending and data states. It displays a loading message while data is being fetched and the data once loaded.

Note:The startTransition would be used with actual data fetching logic in a real application.