This video is available to students only

Use Custom Types to Type the Hook Parameters

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

Currently, all of the different parameters you can pass to the hook are getting hard to read:

export const usePleaseStay = (
  titles: string[],
  animationType: AnimationType,
  faviconLinks: string[],
  interval: number,
  shouldAlwaysPlay: boolean
) => {
  // ...
};

It's a long list of parameters, and when calling the hook, it's easy to get confused about which one is which. Some users may wonder if there is a specific reasoning behind their order. You also could have make a few of the parameters optional and provide reasonable default values. Instead of this long list of function parameters, create a type for these parameters along with default values. First create a new folder under src/ called types/:

mkdir src/types

Then create a new file called UsePleaseStayOptions.ts in that folder:

touch src/types/UsePleaseStayOptions.ts

Now add the following:

import { AnimationType } from "../enums/AnimationType";

export type UsePleaseStayOptions = {
  titles: string[];
  animationType?: AnimationType;
  faviconLinks?: string[];
  interval?: number;
  shouldAlwaysPlay?: boolean;
};

This type removes the suggestion previously that there was a sort of order of the parameters, which for this hook is not the case. While titles is required, it is still only typed as an array of strings, and in the JavaScript world, an empty array, i.e. [] would still be valid, and would break the hook by means of useTitleChangeEffect if passed. You'll use a powerful utility type to ensure that the titles array has at least one. You can create another type under the types folder, which you'll call ArrayOfOneOrMore.ts

touch src/types/ArrayOfOneOrMore.ts

This type simply enforces that for a given type T, that the array of that type has at least one item:

export type ArrayOfOneOrMore<T> = {
  0: T;
} & Array<T>;

Update the UsePleaseStayOptions type to use this new type:

import { AnimationType } from "../enums/AnimationType";
import { ArrayOfOneOrMore } from "./ArrayOfOneOrMore";

export type UsePleaseStayOptions = {
  titles: ArrayOfOneOrMore<string>;
  animationType?: AnimationType;
  faviconLinks?: string[];
  interval?: number;
  shouldAlwaysPlay?: boolean;
};

A similar typing is also true for the faviconLinks parameter. It can remain optional, but it really only makes sense if it contains at least two entries. A single entry array wouldn't actually lead to any toggling of favicons. Therefore you can make another utility type ArrayOfTwoOrMore:

export type ArrayOfTwoOrMore<T> = {
  0: T;
  1: T;
} & Array<T>;

And you can add this to UsePleaseStayOptions:

import { AnimationType } from "../enums/AnimationType";
import { ArrayOfOneOrMore } from "./ArrayOfOneOrMore";
import { ArrayOfTwoOrMore } from "./ArrayOfTwoOrMore";

export type UsePleaseStayOptions = {
  titles: ArrayOfOneOrMore<string>;
  animationType?: ArrayOfTwoOrMore<string>;
  faviconLinks?: string[];
  interval?: number;
  shouldAlwaysPlay?: boolean;
};

Back in usePleaseStay.ts, you should import this type and use it to type the hook parameters:

// ...
import { UsePleaseStayOptions } from "./../types/UsePleaseStayOptions";
// ...

export const usePleaseStay = ({
  titles,
  animationType,
  faviconLinks,
  interval,
  shouldAlwaysPlay,
}: UsePleaseStayOptions) => {
  // ...
};

Adding Default Values

Other than titles, the single required parameter, all the other parameters are optional. You can add default values to these parameters by adding = <value> to the end of the parameter. Apply sensible default values to each of the optional properties within the UsePleaseStayOptions type:

export const usePleaseStay = ({
  titles,
  animationType = AnimationType.LOOP,
  faviconLinks = undefined,
  interval = 500,
  shouldAlwaysPlay = false,
}: UsePleaseStayOptions) => {
  // ...
};

Modifications to the Code

Since many of the parameters are now optional, you need to make a small modifications to the code. Mainly, you need to type and check the value of faviconLinks in useFaviconChangeEffect. First, you can use the UsePleaseStayOptions type for typing faviconLinks:

export const useFaviconChangeEffect = (
  faviconLinks: UsePleaseStayOptions['faviconLinks'],
  // ... other parameters
)

and you should only run the interval in the case when faviconLinks is not undefined:

useInterval(
  () => {
    if (faviconLinks !== undefined) {
      const nextIndex = faviconIndex + 1;
      nextIndex === faviconLinks.length
        ? setFaviconIndex(0)
        : setFaviconIndex(nextIndex);
    }
  },
  interval,
  shouldIterateFavicons
);

and the same guard in the useEffect hook:

useEffect(() => {
  if (faviconLinks !== undefined) {
    faviconRef.current.href = faviconLinks[faviconIndex];
  }
}, [faviconIndex, faviconLinks]);

Documenting the Hook Parameters

With all this work complete, you should document the parameters the hook can accept within the README. Add the following to your README:

## Parameters

Parameters for `usePleaseStay` are passed via an object of [type `UsePleaseStayOptions` (click me!)](./src/types/UsePleaseStayOptions.ts).

| Name               | Type                       | Default Value                                             | Description                                                                                                                                                                                                                                                                                                                                                                                                                        |
| ------------------ | -------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `titles`           | `ArrayOfOneOrMore<string>` | **Required, an array of strings with at least one value** | The titles to iterate through. Note that with certain `AnimationType` only the first title is taken.                                                                                                                                                                                                                                                                                                                               |
| `animationType`    | `AnimationType`            | `AnimationType.LOOP`                                      | The animation type on the title. There are three types:<br/>- `AnimationType.LOOP`: each title in the `titles` is iterated sequentially<br/>- `AnimationType.CASCADE:` Only the first title in the `titles` array is taken, and the title is modified by adding one letter at a time<br/>- AnimationType.MARQUEE - only the first title in the `titles` array is taken, and the title animates across the tab in a marquee fashion |
| `faviconLinks`     | `ArrayOfTwoOrMore<string>` | `undefined`                                               | The desired favicon links to animate through                                                                                                                                                                                                                                                                                                                                                                                       |
| `interval`         | `number`                   | `500`                                                     | The rate in milliseconds at which the title and favicon change occurs                                                                                                                                                                                                                                                                                                                                                              |
| `shouldAlwaysPlay` | `boolean`                  | `false`                                                   | The rate in milliseconds at which the title and favicon change occurs                                                                                                                                                                                                                                                                                                                                                              |

This lesson preview is part of the Master Custom React Hooks with TypeScript course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

Unlock This Course

Get unlimited access to Master Custom React Hooks with TypeScript, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Master Custom React Hooks with TypeScript