challenges

Next.js Data Fetching: Part 5 (Solution)

You can find a summary of the code updates in this pull request . Read on for explanation.

Why custom hooks?

We use custom hooks for the same reasons we use functions – either because

  1. we want to re-use the same logic over and over, or
  2. we want to abstract the details of the logic away from where the logic is called.

In this case, we’re creating a custom hook for the second reason. We don’t intend to re-use this logic anywhere else, but it’s nice to have the logic tucked away in its own file. Someone reading the Home component doesn’t need all those details up front, and the complicated logic gets in the way of understanding how the component works at a high level.

1. Create custom hook file

We’ll create a custom hook file in /src/component/hooks/use-quote-styles.ts. (You’ll need to create the folder as well, since this is our first hook and the hooks folder doesn’t exist yet.)

Within use-quote-styles.ts, we can declare and export the custom hook. We’ll call it useQuoteStyles, since (all hooks start with use ).

use-quote-styles.ts

function useQuoteStyles() {
 
}
 
export default useQuoteStyles;

2. Move the logic

Now we’ll move all the logic (including state) from the Home component in page.tsx to useQuoteStyles, and return all state plus the handleClick function.

A couple notes:

  • We’ll need to move the Status type over, too, since it’s used in this file now and not page.tsx.
  • I used the name fetchQuoteStyles for the handleClick function, because handleClick doesn’t mean as much when it’s not defined right next to the button whose click is being handled.

use-quote-styles.ts

import React from "react";
 
type Status = "idle" | "loading" | "error";
 
function useQuoteStyles() {
  const [quote, setQuote] = React.useState<string>();
  const [status, setStatus] = React.useState<Status>("idle");
  const [error, setError] = React.useState<string>();
 
  const fetchQuoteStyles = async () => {
    // reset error
    setError(undefined);
 
    try {
      // start request
      setStatus("loading");
      const response = 
        await fetch("/api/get-quote-styles");
      if (!response.ok) {
        throw new Error(response.statusText);
      }
 
      const json = await response.json();
      if (!json?.quote) {
        throw new Error("Malformed response");
      }
 
      setQuote(json.quote);
      setStatus("idle");
    } catch (error) {
      setError(error?.toString());
      setStatus("error");
    }
  };
 
  // return all of the state and `fetchQuoteStyles`
  return { 
    status, 
    error, 
    quote, 
    fetchQuoteStyles 
  };
}
 
export default useQuoteStyles;

3. Call the hook

Now we need to call the hook from the Home component, destructuring the object from the return value. We’ll also need to update the onClick value for Button.

page.tsx

// ...
import useQuoteStyles from "@/hooks/use-quote-styles"
// ...
 
export default function Home() {
  const { 
    status, 
    error, 
    quote, 
    fetchQuoteStyles 
  } = useQuoteStyles();
 
  return (
    <main>
      <Button onClick={fetchQuoteStyles}>
        use random quote
      </Button>
      {/* ... */} 
    </main>
  );
}

Up next

Now that the logic is all nicely in place, we’ll update the UI in the next workshop to reflect the loading and error states.