← JumboCode Reference Sheets

Workshop C

Click here to get the starter code (using Next.js).

Here's how your React component will start:

// app/page.tsx
"use client";

export default function Home() {
  return <div></div>;
}

I've built out an API route, which just returns a list of events.

It responds to GET requests to the URL /api/events. GET is a type of request (a "verb") that you can make to a URL over the internet — other types include POST (usually for creating data), PATCH (for updating data), and DELETE.

When someone makes a GET request, it uses Prisma to fetch data from a database, and then returns it.

If we navigate to /api/events in our browser, we'll actually see the data! This is because our browser makes GET requests when we visit a URL.

// app/api/events/route.ts
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();

export const GET = async () => {
  const events = await prisma.event.findMany();

  return new Response(JSON.stringify({ events: events }));
};

This URL is called an "endpoint" or an "API". An endpoint is just a fancy way of saying "a URL that gets data or saves data when we make a request to it". An API is any interface through which programs communicate — in this case, it's an interface that programs communicate with over the internet. (It could be our website program, or another website program if they want to load our list of events and we want to allow them to do that.)


First, let's create a button that prints a log when clicked.

You can find the logs by clicking the "bug" icon in the CodeSandbox "browser" toolbar, and then "Console" in the debugger that pops up.

"use client";

export default function Home() {
  const handleClick = () => {
    console.log("Clicked!");
  };

  return (
    <div>
      <button onClick={handleClick}>Show events</button>
    </div>
  );
}

We'll use the built-in fetch function that's built in to JavaScript to make a GET request to the /api/events URL. Notice that we're not specifying a verb, but GET is the default.

If you open the console, you'll see the data being loaded and logged when you press the button!

The .then syntax is a part of JavaScript called "Promises," which allows us to wait for a function that takes time to run. If we immediately moved on, the data wouldn't be loaded yet! Promises can be tricky, so feel free to just copy this example.

The first .then converts the response from the URL into a variable using something called JSON format. (JSON format is a way to transport whole variables across the internet as a string. The backend turns the variable into a string, then the frontend turns that string back into a variable.)

"use client";

export default function Home() {
  const handleClick = () => {
    fetch("/api/events")
      .then((res) => res.json())
      .then((json) => {
        console.log(json);
      });
  };

  return (
    <div>
      <button onClick={handleClick}>Show events</button>
    </div>
  );
}

You'll notice that the data we really want is under json.events. You can console.log(json.events) and see that we only get the array of events.


We need somewhere to store the data! This is where React's very fundamental concept of State comes in. State is a special type of variable that we can use to store the "state of our application" — aka anything that impacts how the app looks or behaves.

We use state as in the example below, with a function called useState that provides us both the variable (events) and a function to update the variable (setEvents). We should only use setEvents to update events (never events = ...), so React knows when the state updates and knows to update the things on the screen accordingly.

The argument that we pass into useState is the initial value of the state.

useState is one of the function that React provides and calls "Hooks" because they allow us to hook into React and add our own functionality.

"use client";
import { useState } from "react";

export default function Home() {
  const [events, setEvents] = useState([]);

  const handleClick = () => {
    fetch("/api/events")
      .then((res) => res.json())
      .then((json) => {
        setEvents(json.events);
      });
  };

  return (
    <div>
      <button onClick={handleClick}>Show events</button>
    </div>
  );
}

Now let's put that piece of state to good use! We'll use the React loops we covered in Workshop B to show the list of events on the page.

"use client";
import { useState } from "react";

export default function Home() {
  const [events, setEvents] = useState([]);

  const handleClick = () => {
    fetch("/api/events")
      .then((res) => res.json())
      .then((json) => {
        setEvents(json.events);
      });
  };

  return (
    <div>
      <button onClick={handleClick}>Show events</button>

      {events.map((event) => (
        <div key={event.name}>
          <p>{event.name}</p>
          <i>{event.time}</i>
        </div>
      ))}
    </div>
  );
}

Notice that we're specifying a key on the outermost element within the loop. This allows React to render the list efficiently, and it's required to be a unique identifier for each row.

Remember that we use map to make loops in React because they allow us to transform one array into another array. We're turning an array of event data into an array of React components that display that event data, which React knows how to show on the page.

Also note that the events initially starts as an empty array ([]), so even though we're rendering it on the page, it doesn't show up as anything because it's looping over nothing.


Pretty slick! We've now got the data loading when we click a button!

But what if we want the data to load immediately when the page loads?


We could take the fetch out of the function and just run it without waiting for the button press to happen:

export default function Home() {
  const [events, setEvents] = useState([]);

  // Anti-example — don't do this!!
  fetch("/api/events")
    .then((res) => res.json())
    .then((json) => {
      setEvents(json.events);
    });

  return ...
}

This will work, but it's a bad idea. Recall that React components are just functions that return HTML. Every time the React component is rendered on the screen, the function is executed to figure out what should be shown on the screen.

React reserves the right to re-render your component anytime it wants, and it does so whenever state in your application changes that could impact this component. That means that if one of the parents of this component changes state, this one will also get re-rendered just in case.

In general, React will re-render your components when it sees fit. And every time your component gets re-rendered, we're making another request to the API, which wastes the user's bandwidth and our server resources. We only want to load this data once, when the page loads.


"use client";
import { useState, useEffect } from "react";

export default function Home() {
  const [events, setEvents] = useState([]);

  useEffect(() => {
    fetch("/api/events")
      .then((res) => res.json())
      .then((json) => {
        setEvents(json.events);
      });
  }, []);

  return (
    <div>
      {events.map((event) => (
        <div key={event.name}>
          <p>{event.name}</p>
          <i>{event.time}</i>
        </div>
      ))}
    </div>
  );
}

Instead, we'll use another Hook from React called useEffect.

useEffect takes two arguments: the first is the function that it should run, and the second is an array of variables that it should "watch". When one of those variables changes, the function you gave to useEffect is executed.

Imagine that you want to send a notification every time a piece of state called count is updated (from 0, to 1, to 2, etc). You could use a useEffect like this:

const [count, setCount] = useState(0)

useEffect(() => {
  // Send notification
}, [count])

The other thing to know about useEffect is that it runs the function once when the page loads. Therefore if we don't tell it to watch any variables it will still fire once when the page loads. But it will fire only once, unlike the previous example, which is exactly as we want.


And with that, we have the data loading and showing on the page when the page loads!

As a recap: When the page loads, our useEffect function makes a GET request to an API endpoint (URL) using the built-in fetch function. That endpoint uses Prisma to get data from the database, and returns it over the internet. Our frontend takes that data, stores it in React state (created using the useState hook), and React sees the updated state and automatically updates the UI to reflect the change.

👋

Have questions?