Introduction of React Hooks with Examples

React

Introduction

In this article, we are looking into the basics of hooks and their types. Also, there are a few real-time use cases and examples.

Agenda

  1. What are Hooks?
  2. Why Hooks?
  3. Types of Hooks
  4. Examples of Hooks

Prerequisites

  1. NPM
  2. React JS
  3. VS Code

What are Hooks?

Hooks are special functions in React that let you use state and other React features without writing a class.

Why Hooks?

Hooks allow functional components to manage state and lifecycle events, which were previously only possible in class components.

Hooks simplify the way React developers manage state and side effects, making functional components more powerful and flexible.

Types of Hooks
 

1. useState

Allows you to add state to functional components.

Example

import React, { useState } from 'react';
function UseStateDemo() {
  // Declare a state variable called `count` with an initial value of 0
  const [count, setCount] = useState(0);
  // Function to increment the count
  const increment = () => {
    setCount(count + 1);
  };
  // Function to decrement the count
  const decrement = () => {
    set count(count - 1);
  };
  return (
    <div className="UseStateDemo">
      <h1>Counter App</h1>
      <p>Current Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <button onClick={decrement}>Decrement</button>
    </div>
  );
}
export default UseStateDemo;

Explanation

  1. Importing useState: useState is imported from React to allow the functional component to manage the state.
  2. Declaring State
    const [count, setCount] = useState(0);
    
    • count is the current state variable, initialized to 0.
    • setCount is the function used to update the count state.
  3. Increment Function
    const increment = () => {
        setCount(count + 1);
    };
    
    • This function increases the count value by 1 whenever it's called.
  4. Decrement Function
    const decrement = () => {
      setCount(count - 1);
    };
    
    • This function decreases the count value by 1 whenever its called.
  5. Returning JSX: The component returns JSX that displays the current count value and two buttons that trigger the increment and decrement functions when clicked.

Example Usage

When you click the “Increment” button, the count will increase by 1.

When you click the “Decrement” button, the count will decrease by 1.

Navigation

Counter App

This example clearly shows how useState can be used to manage local states within a functional component, allowing you to create interactive and dynamic components in React.

2. useEffect

Allows you to perform side effects in your function components, such as data fetching or manually updating the DOM.

Example

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

function UseEffectDemo() {
  // State to store the list of product
  const [product, setProducts] = useState([]);
  // State to handle loading status
  const [loading, setLoading] = useState(true);
  // State to handle errors
  const [error, setError] = useState(null);

  // Fetch products when the component mounts
  useEffect(() => {
    // Define an async function to fetch data
    const fetchProducts = async () => {
      try {
        // Fetch data from API
        const response = await fetch('https://fakestoreapi.com/products');
        // Check if response is ok
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        // Parse the JSON data
        const data = await response.json();
        // Update state with the fetched data
        setProducts(data);
      } catch (error) {
        // Update state with the error
        setError(error);
      } finally {
        // Update loading state
        setLoading(false);
      }
    };

    // Call the async function
    fetchProducts();
  }, []); // Empty dependency array means this effect runs once when the component mounts

  // Render loading, error, or product data
  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div className="ProductList">
    <h1>Product List</h1>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Name</th>
          <th>Price</th>
        </tr>
      </thead>
      <tbody>
        {product.map(product => (
          <tr key={product.id}>
            <td>{product.id}</td>
            <td>{product.title}</td>
            <td>${product.price.toFixed(2)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  </div>
  );
}
export default UseEffectDemo;

Explanation

  1. State Management
    • product: Holds the list of products fetched from the API.
    • loading: Tracks whether the data is still being fetched.
    • error: Stores any error that occurs during the fetch process.
  2. Fetching Data with useEffect
    • The useEffect Hook is used to perform side effects — in this case, fetching data from the API.
    • The effect runs only once when the component mounts because the dependency array is empty ([]).
    • Inside the useEffect, an asynchronous function fetchProducts is defined and immediately invoked to fetch product data from https://fakestoreapi.com/products.
      • The function handles
        1. Successful Fetching: The fetched data is stored in the product state.
        2. Error Handling: Any errors encountered during the fetch are stored in the error state.
        3. Loading State: The loading state is set to false once the data fetch is complete (regardless of success or failure).
  3. Rendering Logic
    • Loading State: While the data is being fetched, the component renders a loading message: <p>Loading…</p>.
    • Error State: If an error occurs, an error message is displayed: <p>Error: {error.message}</p>.
    • Data Display: Once the data is fetched successfully, the component renders a table listing the products with their IDs, names, and prices.

Example Usage

When the UseEffectDemo component mounts, it automatically fetches the list of products from the provided API.

Depending on the fetch result, it will either show the products, an error message, or a loading indicator.

Product list

This component showcases how to effectively use useEffect to perform side effects like data fetching in a React functional component, alongside managing the various states related to the fetch process (loading, error, and data).

3. useReducer

This hook is an alternative to useState for managing complex state logic.

Example

import React, { useReducer } from 'react';

// Define the initial state
const initialState = {
  count: 0,
  name: '',
  isEditing: false,
};

// Define the reducer function
const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
    case 'DECREMENT':
      return { ...state, count: state.count - 1 };
    case 'TOGGLE_EDIT':
      return { ...state, isEditing: !state.isEditing };
    case 'SET_NAME':
      return { ...state, name: action.payload };
    default:
      return state;
  }
};

// Component
const UseReducerDemo = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  const handleIncrement = () => dispatch({ type: 'INCREMENT' });
  const handleDecrement = () => dispatch({ type: 'DECREMENT' });
  const handleToggleEdit = () => dispatch({ type: 'TOGGLE_EDIT' });
  const handleNameChange = (e) => dispatch({ type: 'SET_NAME', payload: e.target.value });

  return (
    <div>
      <h1>UseReducer Demo</h1>
      <div>
        <h2>Count: {state.count}</h2>
        <button onClick={handleIncrement}>Increment</button>
        <button onClick={handleDecrement}>Decrement</button>
      </div>
      <div>
        <h2>Name: {state.name}</h2>
        <button onClick={handleToggleEdit}>
          {state.isEditing ? 'Stop Editing' : 'Edit Name'}
        </button>
        {state.isEditing && (
          <input
            type="text"
            value={state.name}
            onChange={handleNameChange}
            placeholder="Enter your name"
          />
        )}
      </div>
    </div>
  );
};

export default UseReducerDemo;

Explanation

  1. Initial State: The initialState object defines the initial values for.
    • count: A numeric counter, starting at 0.
    • name: A string, initially empty, to store the user’s name.
    • editing: A boolean that tracks whether the name is being edited, initially set to false.
  2. Reducer Function: The reducer function determines how the state should change based on the dispatched action.
    • INCREMENT: Increases the count by 1.
    • DECREMENT: Decreases the count by 1.
    • TOGGLE_EDIT: Toggles the isEditing state between true and false.
    • SET_NAME: Updates the name state with the value provided in action. payload.
    • Default Case: Returns the current state if the action type doesn’t match any case.
  3. Using useReducer
    • The useReducer Hook is used to manage the component’s state. It takes the reducer function and initialState as arguments and returns the current state and a dispatch function.
    • dispatch is used to send actions to the reducer, triggering state changes based on the action type.
  4. Event Handlers
    • handleIncrement: Dispatches the INCREMENT action to increase the count.
    • handleDecrement: Dispatches the DECREMENT action to decrease the count.
    • handleToggleEdit: Dispatches the TOGGLE_EDIT action to toggle the editing mode.
    • handleNameChange: Dispatches the SET_NAME action with the new name value whenever the input field changes.
  5. Rendering the Component
    • The component renders.
    • The current count with buttons to increment and decrement it.
    • The current name with a button to toggle the editing mode.
    • An input field to edit the name if editing is true.

Example Usage

  1. Increment/Decrement Count: Users can click the buttons to adjust the count.
  2. Toggle Edit Mode: Users can toggle the editing mode for the name input.
  3. Update Name: If editing mode is active, users can type in the input field to update the name state.

Benefits of useReducer in this Component

  1. Centralized State Management: The reducer function manages all state changes in one place, making the logic easy to follow and maintain.
  2. Scalability: As the state management logic grows more complex, useReducer can handle it more gracefully than useState would with multiple state variables.
  3. Readability: By using actions, the intent of each state change is clearly communicated, improving the readability of the code.
    Demo
    UseReducer
    Edit

This component effectively demonstrates how useReducer can be used to manage state in more complex scenarios, providing a clear structure and making it easier to scale and maintain your application.

4. useContext

Allows you to access context values in a functional component.

Example

import React, { createContext, useState, useContext } from 'react';
// Create a Context with default values
const ThemeContext = createContext();
const ThemeProvider = ({ children }) => {
  // State to manage the theme
  const [theme, setTheme] = useState('light');
  // Function to toggle the theme
  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light'));
  };
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};
const ThemeSwitcher = () => {
  // Use the context in a component
  const { theme, toggleTheme } = useContext(ThemeContext);
  return (
    <div style={{ padding: '20px', backgroundColor: theme === 'light' ? '#f0f0f0' : '#333', color: theme === 'light' ? '#000' : '#fff' }}>
      <h1>Current Theme: {theme}</h1>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </div>
  );
};
// Main component using the ThemeProvider
const UseContextDemo = () => {
  return (
    <ThemeProvider>
      <ThemeSwitcher />
    </ThemeProvider>
  );
};
export default UseContextDemo;

Explanation

  1. Creating the Context
    • ThemeContext: Created using createContext(). It acts as a container to hold the state and functions related to the theme.
    • The context is initialized without a default value since it’s set within the ThemeProvider.
  2. ThemeProvider Component
    • State Management: The theme state is managed using useState, with “light” as the initial value.
    • toggleTheme Function: This function switches the theme between “light” and “dark” using the previous state.
    • ThemeContext.Provider: The provider wraps around children (any components passed to ThemeProvider), allowing them to access the theme state and toggle the theme function via context.
  3. ThemeSwitcher Component
    • Using the Context: The useContext(ThemeContext) hook is used to consume the context, giving access to the theme and toggle the theme.
    • Rendering
      1. The background color and text color of the div change based on the current theme.
      2. A button is provided to toggle the theme, invoking the toggleTheme function when clicked.
  4. UseContextDemo Component
  5. ThemeProvider: Wraps the ThemeSwitcher component, ensuring it and any nested components have access to the ThemeContext.
  6. This structure allows the ThemeSwitcher to receive and use the theme and toggle the theme without needing props passed down through multiple components.

Benefits of Using Context

  1. Avoiding Prop Drilling: The ThemeContext allows the theme state and functions to be shared directly with components that need them, without passing them down as props through multiple layers of components.
  2. Centralized State Management: The ThemeProvider centralizes the logic for managing and updating the theme, making the application easier to maintain and extend.
  3. Reusability: Any component within the ThemeProvider can access the theme context, making it easy to build a consistent UI across different parts of the application.

Example Use Case

Theming Across an Application: This pattern is commonly used in applications to provide global themes, language settings, user authentication, or any other shared state that needs to be accessed by multiple components.

Current Theme

Toggle theme

Your implementation is clean, and the use of use context in combination with a context provider demonstrates an effective way to manage the global state in a React application.

5. useMemo

Memorizes a value, re-computing it only when its dependencies change.

Example

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

// Expensive calculation function
const calculateFactorial = (num) => {
  console.log('Calculating factorial...');
  let result = 1;
  for (let i = 1; i <= num; i++) {
    result *= i;
  }
  return result;
};

const UseMemoDemo = () => {
  const [count, setCount] = useState(0);
  const [number, setNumber] = useState(1);

  // Memoized factorial calculation
  const factorial = useMemo(() => calculateFactorial(number), [number]);

  return (
    <div>
      <h1>UseMemo Demo</h1>
      <div>
        <h2>Counter: {count}</h2>
        <button onClick={() => setCount(count + 1)}>Increment Counter</button>
      </div>
      <div>
        <h2>Factorial of {number}: {factorial}</h2>
        <input
          type="number"
          value={number}
          onChange={(e) => setNumber(Number(e.target.value))}
        />
      </div>
    </div>
  );
};

export default UseMemoDemo;

Explanation

  1. State Variables
    • count: Tracks the count of button clicks, initialized to 0.
    • number: Stores the number for which the factorial will be calculated, initialized to 1.
  2. Expensive Calculation Function
    • calculate factorial (num): This function calculates the factorial of a given number num. The calculation is performed in a loop, which could become expensive for larger numbers.
    • The function logs “Calculating factorial…” to the console each time it runs, which helps to see when the calculation is actually being performed.
  3. Memoizing the Factorial Calculation
    useMemo(() => calculateFactorial(number), [number]);
    
    • useMemo is used to memoize the result of calculating factorial (number).
    • The calculation is only re-executed when the number value changes. If the number stays the same, the cached result is returned, avoiding unnecessary recalculations.
    • This optimization is particularly useful when the calculation is computationally expensive, as it prevents the factorial calculation from being re-executed on every render if a number hasn’t changed.
  4. Rendering the Component
    1. Counter Section
    2. Displays the current count and includes a button to increment the count.
    3. The counter is independent of the factorial calculation, so clicking the button will not re-trigger the factorial calculation due to useMemo.
  5. Factorial Section
    • Displays the factorial of the current number.
    • Includes an input field to change the number, which will trigger the recalculation of the factorial if the number is modified.

Benefits of useMemo in This Component

  1. Performance Optimization: By memoizing the factorial calculation, the component avoids unnecessary recomputation, improving performance, especially when dealing with large numbers or complex calculations.
  2. Efficient Rendering: The component only recalculates the factorial when necessary (i.e., when number changes), ensuring that unrelated state changes (like count) do not trigger expensive operations.

Example Use Case

Heavy Computations: useMemo is ideal in scenarios where you have computationally intensive operations that depend on specific inputs. It ensures that these operations are only performed when absolutely necessary, preventing performance bottlenecks in your application.

Localhost

This component demonstrates the practical use of useMemo to optimize React applications by controlling when expensive computations are performed, ensuring smooth and efficient rendering.

6. Custom hooks

A custom hook in React is a JavaScript function that allows you to reuse stateful logic across multiple components. Custom hooks enable you to extract and share logic that would otherwise be duplicated across components, making your code more modular and easier to maintain.

Example

import React from 'react';
import useLocalStorage from './useLocalStorage';

const CustomHookDemo = () => {
  // Use the custom hook to manage a count value in localStorage
  const [count, setCount] = useLocalStorage('count', 0);

  return (
    <div style={{ padding: '20px' }}>
      <h1>Custom Hook Demo</h1>
      <h2>Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
};

export default CustomHookDemo;
import { useState, useEffect } from 'react';

// Custom hook to manage localStorage
const useLocalStorage = (key, initialValue) => {
  // Get initial value from localStorage or use 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;
    }
  });

  // Save value to localStorage whenever it changes
  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
};

export default useLocalStorage;

Explanation

This component demonstrates the use of the useLocalStorage custom hook.

  1. Hook Usage
    • The use local storage hook is used to manage the count state, which is persisted in localStorage under the key ‘count’.
    • setCount is a function that updates both the state and local storage.
  2. Component Structure: The component displays the current count and provides three buttons.
    • Increment: Increases the count by 1.
    • Decrement: Decreases the count by 1.
    • Reset: Resets the count to 0.

use local storage Custom Hook

This custom hook encapsulates the logic to synchronize state with localStorage.

  1. State Initialization
    • The hook first checks if a value exists in localStorage under the provided key.
    • If a value exists, it is parsed from JSON and used as the initial state.
    • If no value exists, the initialValue is used as the initial state.
  2. Effect for Updating localStorage
    • The useEffect hook monitors changes to storedValue or key.
    • Whenever storedValue changes, the new value is stored in localStorage.
  3. Error Handling: Both the initialization and the effect include error handling via try-catch blocks to prevent the application from crashing if there’s an issue with local storage.

How Does It Work?

  1. State Persistence: The count state is stored in localStorage, meaning it will persist even if the user refreshes the page or closes and reopens the browser.
  2. Automatic Sync: Any time the count changes, the new value is automatically saved in localStorage.
  3. Reusability: The useLocalStorage hook can be reused in any component that needs to sync state with localStorage.
    Custom Hook

This pattern is especially useful in scenarios where you want user data to persist between sessions, such as saving form inputs, user preferences, or any other application state that needs to survive page reloads.

GitHub: https://github.com/Jaydeep-007/React-Hooks

Conclusion

In this article, we looked into the basics of hooks, types of hooks, and examples with step-by-step implementation.