challenges

Next.js Data Fetching: Part 4 (Solution)

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

1. try/catch block

First, let’s implement a try/catch block for any errors that are thrown in the normal course of business (for example, by the response.json() statement).

Then, if any statement within the try block throws an error, we’ll go straight to the catch block (without finishing the rest of the try block after the error). So we’ll register in the catch block that we’ve seen an error, by setting status to “error”.

page.tsx

const handleClick = async () => {
  try {
    setStatus("loading");
    const response = await fetch("/api/get-quote-styles");
    const json = await response.json();
    setQuote(json.quote);
    setStatus("idle");
  } catch (error) {
    setStatus("error")
  }
};

2. Store error in state

When we create an ErrorCard UI in a future workshop, we’re going to need a string version of the error exception from the catch block. We’ll use the the toString() method to get a string, and store it in a new error state.

Any time we start a new fetch call, we’ll reset the error to undefined, for a clean slate.

page.tsx

export default function Home() {
  // ...
  const [error, setError] = React.useState<string>();
 
  const handleClick = async () => {
    // reset error
    setError(undefined);
 
    try {
      // ...
    } catch (error) {
      setError(error.toString())
      setStatus("error")
    }
  }
 
  // ...
};

You may have seen a complaint from TypeScript here, that error is of type unknown:

A VSCode popover that reads 'error' is of type 'unknown'

It’s true! in JavaScript you don’t have to throw an Error instance – if you want, you could, say, throw the number 8 (I wouldn’t advise it, but it’s allowed). To satisfy TypeScript, we’ll need to use optional chaining here, just in case the error value doesn’t have a toString() method.

setError(error?.toString())

3. More errors

There are a couple situations where we’d want to deliberately throw an error:

  1. if response.ok is falsy
  2. if json doesn’t have a quote property

In either of these cases, we can throw an Error and it will be handled by the catch block. The argument to Error will show up in the error?.toString() value.

  • For the !response.ok condition, we can get an error description in response.statusText , so we’ll use that as the argument to the thrown Error.
  • For the json error, we’ll use a static error string of “Malformed response”.

page.tsx

const handleClick = async () => {
  // reset error
  setError(undefined);
 
  try {
    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");
  }
};

4. Examining state

Let’s add another console.log to the Home component in page.tsx, so we can see the error state value as well:

page.tsx

console.log("STATUS", status);
console.log("ERROR", error);

Then we can do things in src/app/api/get-quote-styles/route.ts to throw errors. Let’s start by throwing an error in the handler:

route.ts

export function GET() {
  throw new Error("kablooie");
  const generatedQuote = getRandomQuote();
 
  return NextResponse.json({ quote: generatedQuote });
}

Here, after clicking the button, ok is false. We get response.statusText as the error` value, which in this case is “Internal Server Error”. (If we wanted to get the “kablooie” string on the client, we’d need to handle the error on the server and send the error string via JSON along with a 500 status. That’s outside the scope of this workshop.)

UI with a 'use random quote' button, a horizontal dotted line, and a blue card with no text. The browser console is open and showing 'STATUS idle'/'ERROR: undefined' then 'STATUS loading'/'ERROR: undefined', and finally 'STATUS error'/'ERROR: Internal Server Error'.

We can trigger the “Malformed response” error by changing the property name (to, say someOtherProperty). Since json doesn’t have a quote property anymore, json?.quote is undefined, which is falsy. This means the !json?.quote condition will evaulate to true, and the “Malformed response” error will be thrown.

route.ts

export function GET() {
  const generatedQuote = getRandomQuote();
 
  return NextResponse.json(
    { someOtherProperty: generatedQuote }
  );
}
UI with a 'use random quote' button, a horizontal dotted line, and a blue card with no text. The browser console is open and showing 'STATUS idle'/'ERROR: undefined' then 'STATUS loading'/'ERROR: undefined', and finally 'STATUS error'/'ERROR: Malformed response'.

Up next

In the next workshop, we’ll move the state and fetch call to a custom hook. This way, someone reading the Home component can get a big-picture view without having to read all the details of the logic.