Fetching data without handling loading states

Loading

In React, when fetching data from an API or performing asynchronous operations, it’s important to manage the loading state to provide a smooth user experience. Not handling loading states properly can lead to a poor user experience, where users might see a blank screen, outdated information, or other issues while waiting for data to load.

Common Issue:

When you fetch data without managing loading states, the component may render before the data is available, or users might not know that the data is still loading. This can result in showing empty content or inconsistent information. Without proper loading indicators (like spinners or loading texts), users may become confused about whether the data is loading, failed, or simply not available.

Example of the Issue:

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

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

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => setData(data));
  }, []);

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

export default DataFetcher;

Issue:

  • No loading indicator: The component doesn’t show anything while the data is being fetched. Users might see an empty screen until the data is fully loaded, which is not ideal.
  • No error handling: There’s also no error handling if the fetch request fails.

Solution: Handling Loading and Error States

To improve the user experience, you can manage the loading and error states. Here’s how to properly handle loading and error states:

  1. Loading State: Display a loading indicator (like a spinner or message) while the data is being fetched.
  2. Error Handling: If the request fails, show an appropriate error message.
  3. Data Rendering: Only display the data once it’s fully loaded.

Corrected Example with Loading and Error Handling:

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

function DataFetcher() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true); // Track loading state
  const [error, setError] = useState(null); // Track errors

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => {
        if (!response.ok) {
          throw new Error('Failed to fetch data');
        }
        return response.json();
      })
      .then(data => {
        setData(data);
        setIsLoading(false); // Data loaded
      })
      .catch(error => {
        setError(error.message);
        setIsLoading(false); // Finished loading, even if failed
      });
  }, []);

  if (isLoading) {
    return <div>Loading...</div>; // Display loading message or spinner
  }

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

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

export default DataFetcher;

Explanation:

  1. isLoading state: A state variable isLoading is used to track whether the data is still being fetched.
    • When the fetch starts, isLoading is set to true.
    • After the data is fetched (or an error occurs), isLoading is set to false.
  2. Error state: The error state captures any errors that occur during the fetch process.
    • If the fetch fails, we set the error state to the error message.
    • If the response status is not OK, we throw an error manually.
  3. Conditional Rendering:
    • If isLoading is true, we render a “Loading…” message or a spinner to indicate the data is still being fetched.
    • If there’s an error, we display the error message.
    • Once data is successfully fetched, we render the data.

Why Handling Loading States is Important:

  • Improved User Experience: Users understand that data is being loaded, and they don’t see a blank screen. It provides feedback, which is a key element of good UI design.
  • Error Feedback: Users know if something went wrong and can take action, like retrying the request or contacting support.
  • Consistency: It ensures that data is properly displayed once it’s ready, rather than showing empty or incorrect information.

Best Practices for Handling Loading States:

  1. Use loading indicators: Use spinners, progress bars, or skeleton loaders to indicate that something is loading. It improves the visual flow.
  2. Error Handling: Always handle possible errors, especially in network requests. Show the user a friendly message if something goes wrong.
  3. Separation of Concerns: Keep the fetching logic in a separate function or a custom hook (like useFetch) to keep your component clean.
  4. Debouncing and Caching: When appropriate, avoid unnecessary re-fetching by using caching techniques or debouncing search queries to improve performance.

Example with Skeleton Loader (Optional):

You can further enhance the user experience by using a skeleton loader (a placeholder element) instead of just a text-based “Loading” message.

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

function DataFetcher() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(response => response.json())
      .then(data => {
        setData(data);
        setIsLoading(false);
      })
      .catch(error => {
        setError(error.message);
        setIsLoading(false);
      });
  }, []);

  if (isLoading) {
    return (
      <div className="skeleton-loader">
        {/* Skeleton loader implementation */}
        <div>Loading...</div>
      </div>
    );
  }

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

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

export default DataFetcher;

Leave a Reply

Your email address will not be published. Required fields are marked *