Data Fetch and Display

Make a component that fetches data from a public API and displays it in a list or table format.

Solution

Data

The following data is sourced from Random User Generator.

Refresh the page to see the loading animation.

Loading...

    Explanation

    Before we start

    Before starting, it's worth mentioning that this is not a barebones approach to creating a data fetching component. In this React component you will see loading messages, error handling, TypeScript types, abstraction and custom hooks.

    The Solution Component

    Focusing at the root level, we can see the custom hook, useFetchUsers() and the structure of the HTML. We pull in users, isLoading and error to output the fetched content from the API.

    Notice the pattern isLoading && <p>Loading...</p>. This is called short-cicuit evaluation and is a quick way to check that the value exists, and if it does, show the component. We did the same with the error component.

    Displaying the data

    It's important to use semantic HTML when structuring your code. This is the proper way to display a list.

    Then we use the map function to produce a list of the users. Notice that we've abstracted the li element into a User component. We supply a key and the user data.

    The User Component

    This is a simple li component that uses optional chaining (the ?) to only execute if the value exists.

    fetchUserData function

    This follows a standard pattern using the fetch API. Note that we are using an async function because it is asynchronous.

    We check to see if there is an OK response header. Before converting the JSON into readable JavaScript and returning the data array.

    useFetchUsers custom hook

    To abstract the logic we've used a custom hook. We keep the state of users, isLoading and error.

    useEffect then runs when the component is loaded, and only once. This is set by leaving the dependency array empty.

    Since the fetch is asynchronous, we use an async function and a trycatch block.

    Note the use of finally to reset loading to false regardless of outcome.

    To finish our custom hook we return the useful variables.

    Code

    import { useState, useEffect } from "react";
    
    // types
    type userProps = {
      gender: string;
      name: {
        title: string;
        first: string;
        last: string;
      };
      email: string;
    };
    
    type allUserProps = userProps[];
    
    type APIResponse = {
      results: userProps[];
    };
    
    // function
    const Solution = () => {
      const { users, isLoading, error } = useFetchUsers();
    
      return (
        <div>
          <h2>Data</h2>
          <p>
            The following data is sourced from{" "}
            <a href="https://randomuser.me">Random User Generator</a>.
          </p>
          <p>Refresh the page to see the loading animation.</p>
    
          {isLoading && <p>Loading...</p>}
          {error && <p>Error: {error}</p>}
    
          <ul>
            {users && users.map((user) => <User key={user.email} {...user} />)}
          </ul>
        </div>
      );
    };
    
    export default Solution;
    
    // display user
    const User = (user: userProps) => {
      return (
        <li>
          {user?.name?.title} {user?.name?.first} {user?.name?.last}
        </li>
      );
    };
    
    // fetch data
    const fetchUserData = async (): Promise<allUserProps> => {
      const response = await fetch("https://randomuser.me/api?results=5&nat=gb");
    
      if (!response.ok) throw Error("Error fetching data");
    
      const data: APIResponse = await response.json();
      return data.results;
    };
    
    // custom hook
    function useFetchUsers() {
      const [users, setUsers] = useState<allUserProps>([]);
      const [isLoading, setIsLoading] = useState<boolean>(true);
      const [error, setError] = useState<string | null>(null);
    
      useEffect(() => {
        const fetchData = async () => {
          try {
            const results = await fetchUserData();
            if (results.length > 0) {
              setUsers(results);
              setError(null);
            } else {
              throw Error("No results found.");
            }
          } catch (error) {
            setError(
              error instanceof Error ? error.message : "An unknown error occured"
            );
          } finally {
            setIsLoading(false);
          }
        };
        fetchData();
      }, []);
      return { users, isLoading, error };
    }
    

    Styling

    // None

    Links