Use Custom Types to Type the Hook Parameters
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo 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.
Get unlimited access to Master Custom React Hooks with TypeScript, plus 70+ \newline books, guides and courses with the \newline Pro subscription.