Custom Hooks

One thing that's pretty special about hooks is their composability. You can use hooks to make other hooks! People tend to call these custom hooks. There are even people who go as far to say "never make an API request in a component, always do it in a hook." I don't know if I'm as hardcore as that but I see the logic in it. If you make a custom hook for those sorts of things they become individually testable and do a good job to separate your display of data and your logic to acquire data. I'm more in the camp of make custom hooks for either complicated logic or reusable logic, but for simple cases it's okay to keep things simple.

Okay, so we want to add a "Pizza of the Day" banner at the bottom of our page. This necessitates calling a special API to get the pizza of the day (which should change every day based on your computer's time.) Let's first write the component that's going to use it.

Make a file called PizzaOfTheDay.jsx

import { usePizzaOfTheDay } from "./usePizzaOfTheDay";

// feel free to change en-US / USD to your locale
const intl = new Intl.NumberFormat("en-US", {
  style: "currency",
  currency: "USD",
});

const PizzaOfTheDay = () => {
  const pizzaOfTheDay = usePizzaOfTheDay();

  if (!pizzaOfTheDay) {
    return <div>Loading...</div>;
  }

  return (
    <div className="pizza-of-the-day">
      <h2>Pizza of the Day</h2>
      <div>
        <div className="pizza-of-the-day-info">
          <h3>{pizzaOfTheDay.name}</h3>
          <p>{pizzaOfTheDay.description}</p>
          <p className="pizza-of-the-day-price">
            From: <span>{intl.format(pizzaOfTheDay.sizes.S)}</span>
          </p>
        </div>
        <img
          className="pizza-of-the-day-image"
          src={pizzaOfTheDay.image}
          alt={pizzaOfTheDay.name}
        />
      </div>
    </div>
  );
};

export default PizzaOfTheDay;
  • The cool part here is the usePizzaOfTheDay(). We now just get to rely on that this going to provide us with the pizza of the day from within the black box of the hook working.

Okay, let's go make the hook! Make a file called usePizzaOfTheDay.jsx (you could call this .js instead of jsx but in a React project I just use JSX for all "React-y" things)

import { useState, useEffect } from "react";

export const usePizzaOfTheDay = () => {
  const [pizzaOfTheDay, setPizzaOfTheDay] = useState(null);

  useEffect(() => {
    async function fetchPizzaOfTheDay() {
      const response = await fetch("/api/pizza-of-the-day");
      const data = await response.json();
      setPizzaOfTheDay(data);
    }

    fetchPizzaOfTheDay();
  }, []);

  return pizzaOfTheDay;
};
  • This looks like what you'd see at the top of a component, right? Now it's just encapsulated as a hook which makes it easy to test by itself! 🎉
  • We can use useState, useEffect, or any hook we want here! We can even use other custom hooks!

Lastly, let's go add this to App.jsx so our component renders.

// import at top
import PizzaOfTheDay from "./PizzaOfTheDay";

// under <Order />
<PizzaOfTheDay />

🏁 Click here to see the state of the project up until now: 06-custom-hooks

Practice Exercise

Create your own custom hook for a small piece of reusable logic.

  1. Make a new hook called useGreeting.jsx.
  2. Use useState to store a name and a greeting message.
  3. Return the current greeting from the hook.
  4. Build a small component that uses the hook and displays the greeting in the UI.
  5. Try changing the name and confirm the greeting updates.