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, backgroundColorThe 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:
(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:
quotestate starts out asundefined(since we didn’t pass an argument touseState), so theCardis empty.handleClickwill run on button click.- After the
fetchcall is complete, theresponsevariable will contain theResponseobject returned fromfetch. - After the
.json()method has completed, thejsonvariable will contain the JSON fromresponse, and thequotestate will be set tojson.quote. - Because state has been updated the component will re-render.
- On the next render,
quotewill have the value, and it will show in theCard.
Up next
We’ll improve the user experience by adding a loading spinner while the fetch is in progress.
Workshops in this series
- Quotation endpoint
- Display fetched data
- Loading state
- Error state
- Custom hook
- Conditional rendering