Section

Client-Side Data Fetching with React Hooks

Part of The Prince Academy's AI & DX engineering stack.

Follow The Prince Academy Inc.

While Next.js excels at server-side and static data fetching, there are many scenarios where fetching data on the client-side after the initial page load is the most appropriate approach. This is particularly useful for data that is dynamic, user-specific, or changes frequently, and where SEO is not a primary concern for that particular data. We'll leverage React's built-in hooks, primarily useState and useEffect, to achieve this.

Client-side data fetching in Next.js behaves very similarly to how you'd do it in any other React application. The key difference is that the data is fetched after the initial HTML has been rendered and sent to the browser. This means the initial page load will be faster, but the content dependent on this fetched data will appear shortly after.

Let's break down the process:

  1. State Management: We'll use useState to store the data fetched from the API, as well as any loading or error states.
  2. Side Effects: The useEffect hook is crucial for performing side effects, such as making API calls. It allows us to run code after the component has rendered.
  3. API Calls: Inside useEffect, we'll make our asynchronous API requests (e.g., using fetch or a library like Axios).
  4. Updating State: Once the data is fetched, we'll update our component's state with the received data. We'll also handle potential errors and set a loading state to provide a better user experience.
import React, { useState, useEffect } from 'react';

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

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await fetch('/api/mydata'); // Example API endpoint
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const result = await response.json();
        setData(result);
      } catch (e) {
        setError(e);
      } finally {
        setLoading(false);
      }
    };

    fetchData();
  }, []); // The empty dependency array ensures this effect runs only once after the initial render

  if (loading) {
    return <div>Loading...</div>;
  }

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

  return (
    <div>
      <h1>My Data</h1>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}

export default MyComponent;

In this example:

  • useState is used to manage data, loading, and error states.
  • useEffect is used to perform the asynchronous data fetching when the component mounts. The empty dependency array [] ensures that this effect runs only once, after the initial render.
  • The fetchData async function handles the API call, including basic error handling.
  • The component conditionally renders different content based on the loading and error states, providing a user-friendly experience.
graph TD;
    A[Component Mounts] --> B{useEffect Runs};
    B --> C[Start Fetching Data];
    C --> D{API Call Successful?};
    D -- Yes --> E[Update State with Data];
    D -- No --> F[Update State with Error];
    E --> G[Render Data];
    F --> G[Render Error Message];
    G --> H[Component Renders];

It's important to consider that data fetched client-side is not immediately available to search engine crawlers. If SEO is critical for the data you're fetching, you should explore Next.js's server-side rendering (SSR) or static site generation (SSG) methods covered in other chapters. However, for dynamic content like user dashboards, shopping cart details, or personalized recommendations, client-side fetching is often the most performant and straightforward solution.