challenges

Next.js Data Fetching: Part 2 (Solution)

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

1. Basic page layout

Let’s start with the basic page layout. There will be a Button with text “use random quote”, a Separator and a Card. We’ll add these to the Home component in src/app/page.tsx, which displays at the top level of the app.

Note, @ is the default alias for the src directory. So "@/components/Button" is equivalent to an absolute path of "src/components/Button" (starting at the top level of the project). This keeps us from having to use relative imports (like "../components/Button"). The relative path isn’t too bad here, since we only need to go up one level. But it can get messy if you have to go up half a dozen levels ("../../../../../../") before you get to the folder you need.

page.tsx

import Button from "@/components/Button";
import Card from "@/components/Card";
import Separator from "@/components/Separator";
 
export default function Home {
  return (
    <main>
      {/* no need to add variant, 
      since "filled" is default */}
      <Button>use random quote</Button>
      <Separator />
      <Card />
    </main>
  );
}

Card props

The above code will generate a TypeScript error for Card that we’re missing some props:

Type '{}' is missing the following properties from 
type 'CardProps': textColor, backgroundColor

The colors don’t really matter for now (in the next series, they’ll be selected by AI based on the quote content). I chose aliceBlue and mediumBlue from the named web colors ]:

<Card textColor="aliceBlue" backgroundColor="mediumBlue" />

If we start up the app with npm run dev and visit http://localhost:3000 , it looks like this:

a web page with a button, dotted separator and empty blue card

(The empty card on page load is not a great user experience; we’ll take care of that in the next workshop.)

2. Add onClick handler

All right, now let’s add an onClick handler for the Button. We’ll create a function called (conventionally) handleClick and then run fetch to access the endpoint. Since fetch is an async function , we need to await it and specify handleClick as async.

page.tsx

export default function Home() {
  const handleClick = async () => {
    const response = await fetch("/api/get-quote-styles");
  };
  
  return (
    <main>
      <Button onClick={handleClick}>
        use random quote
      </Button>
      {/* ... */}
    </main>
  )
}

Get JSON

Great! Now we’ve contacted the server and stored the response in a variable. To get the JSON from the Response object , we need to run the (async) method .json()

page.tsx

const handleClick = async () => {
  const response = await fetch("/api/get-quote-styles");
  const json = await response.json();
};

Add state

Ok, so we’ve got the JSON. How do we display the quote in the UI? Because handleClick is async, the only way to update the component after we get the response is to use state . Here’s what that looks like:

page.tsx

export default function Home() {
  // `<string>` is for TypeScript, since
  //    this state starts out as undefined
  const [quote, setQuote] = React.useState<string>();
 
  const handleClick = async () => {
    const response = await fetch("/api/get-quote-styles");
    const json = await response.json();
    // expecting JSON to have a `quote` property,
    // based on the route handler from last workshop
    setQuote(json.quote);
  };
  
  return (
    {/* ... */}
  )
}

Now we have the json.quote value from the response stored in the state variable quote.

Display the quote

Since the quote state contains the display value, we can plunk it into the Card.

page.tsx

export default function Home() {
  // ...
 
  return (
    <main>
      <Button onClick={handleClick}>
        use random quote
      </Button>
      <Separator />
      <Card 
        textColor="aliceBlue" 
        backgroundColor="mediumBlue"
      >
        {quote}
      </Card>
    </main>
  );  
}

handleClick summary

Here’s the sequence of what will happen when the button is clicked:

  1. quote state starts out as undefined (since we didn’t pass an argument to useState), so the Card is empty.
  2. handleClick will run on button click.
  3. After the fetch call is complete, the response variable will contain the Response object returned from fetch.
  4. After the .json() method has completed, the json variable will contain the JSON from response, and the quote state will be set to json.quote.
  5. Because state has been updated the component will re-render.
  6. On the next render, quote will have the value, and it will show in the Card.
a web page with a button, dotted separator and blue card containing quotation text

Up next

We’ll improve the user experience by adding a loading spinner while the fetch is in progress.