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.

[00:00 - 00:14] Currently, the way we can pass all these different parameters to our hook is starting to get kind of hard to read. It's a long list of parameters and it's easy to get confused on which one is which.
[00:15 - 00:27] Some users may even wonder if there's a specific reasoning behind their order. We also know throughout our implementation we probably could have made some of these parameters optional and provided sensible defaults for each of them.
[00:28 - 00:44] So let's create a type for these parameters and then we'll be able to add default values for some of the optional ones. First we'll create a folder under SRC called types.
[00:45 - 00:58] And I'm going to call this type use please stay options. And so we'll export the type use please stay options.
[00:59 - 01:16] And we know we have our titles which is a string array. We have our animation type which we can make optional and its type is animation type.
[01:17 - 01:31] We have our favor links which is also optional and a string array. We have our interval which is also optional and is a number.
[01:32 - 01:45] And we have should always play optional and Boolean. Now this type will remove the suggestion that there's a sort of order to the parameters which of course for our hook is not the case.
[01:46 - 02:01] And there's a few minor optimizations we can make to this type. We notice that titles is required and there's a sort of loophole in the typings here in that I could pass an empty array and both TypeScript and JavaScript would still be happy with that.
[02:02 - 02:13] And as we know in some of our use title change effect logics if the array is empty that will end up crashing our hook. So we're going to make a utility type to guard against that.
[02:14 - 02:30] Essentially this utility type will require that the string array past the titles has at least one item. So still in the types folder I'm going to create a new type called array of one or more.
[02:31 - 02:41] And that's export type array of one or more. And this will accept a generic type.
[02:42 - 02:58] And we just want that first entry to be the generic type. And with that first entry being the generic type desired we can union that with an array of that generic type.
[02:59 - 03:16] So back in use please stay options we can import that type. And we'll apply it here to the titles parameter instead of a string array we will add a ray of one or more.
[03:17 - 03:31] And the generic type here is string. Now the same is also true for the favicon links even though it's optional if it is passed we should also guard against the empty case.
[03:32 - 03:48] But we should also guard against a single value case that is if we look in our favicon change effect here if favicon links array with just one entry wouldn't exactly break our app here. It just wouldn't do anything.
[03:49 - 03:58] And so I think it's a fair type guard to require that the user pass at least two favicon links. Otherwise the toggle effect that they expect won't happen.
[03:59 - 04:18] So very similar to array of one or more I'm going to copy this and create a type array of two or more. You can rename the type here.
[04:19 - 04:35] And just add the second value to also be that generic type T. So in the use please stay options we will modify this to be the array of two or more.
[04:36 - 04:44] And type string. And in use please stay we can import this type and apply it to our parameters here.
[04:45 - 04:52] So the first thing I'll do is strip off all the types. And I'm going to add a comma.
[04:53 - 05:07] And we're just going to wrap this whole thing in brackets and we can type it according to use please stay options. And as we said the only required parameter here is titles.
[05:08 - 05:22] And with this type of notation it's quite easy to provide default values for all the other optional parameters. So for animation type I'll take animation type loop.
[05:23 - 05:32] And for the favicon links this one will be undefined. Our interval will take 500 milliseconds that we saw in some of the previous lessons.
[05:33 - 05:44] And we'll do should always play set to false. So this keeps the original functionality that we implemented which is the animations will only run when focus is lost.
[05:45 - 06:06] So with these default values set there's only one small change we need to make in our code and that is to account for this undefined favicon links. So if we look in use favicon change effect the first thing we'll do here is ensure that the type is that of the use please stay options.
[06:07 - 06:19] So I'm going to import that type. And of course we don't want the full type but we just want the type of the fav icon link key in that type.
[06:20 - 06:38] And so we know this now is either undefined when it's not passed at all but if it is defined it has at least two elements. And to guard against the case that favicon links is undefined we're just going to wrap the whole use interval function with if favicon links.
[06:39 - 07:05] It's not equal undefined then we will run the increment logic here. And we also should add the same guard down in the use effect just to ensure that we're not going to access favicon links or try to access any index if it's undefined.
[07:06 - 07:19] And we should also add it to our dependency array. With the effort we've put into our custom type for the parameters it's a good point in time that we document all of these parameters.
[07:20 - 07:28] And a good place to document this is in the read me. And I'm not going to bore you with typing out the entire documentation.
[07:29 - 07:45] I'll just paste it in here. But essentially what it is is a markdown table with the name, the type, the default value and a small description of each of the parameters that we have in our use please stay options.
[07:46 - 07:52] And I always find it nice in the GitHub read me when people link directly to the source. And that's what we're using here.
[07:53 - 08:03] And in GitHub you're allowed to link relatively to the read me. And so the use please stay options source file is of course the file we've just made in SRC types use please stay options.
[08:04 - 08:17] We can actually leverage Visual Studios code preview here to get a nice preview of how this looks kind of overflows the page. But you can get an idea of how this documentation table looks.
[08:18 - 08:35] So adding this new type as well as the nice documentation in the read me just makes it that much easier for a developer unfamiliar with our hook to get up to speed on using it quite quickly. So now let's build the hook and make sure everything's still okay.
[08:36 - 08:42] And moving into the example app. We can open up the app.tsx.
[08:43 - 08:56] And we already noticed right away there's some errors of course because the signature of the hook has totally changed. And the first thing I want to illustrate is our one or more type that we've set on the titles.
[08:57 - 09:05] So of course we pass an object now since our type is an object. And if we go to set titles we can't just pass an empty array.
[09:06 - 09:17] That was the whole point of the utility type that we made. And so we see of course type empty array is unassignable to type array of one or more string.
[09:18 - 09:31] Property zero is missing in type empty array but required. So of course it's telling us as soon as we have something like or even just an empty string it's satisfied.
[09:32 - 09:44] And the same is true for the favicon links. If I try and pass an empty array it's of course going to yell at me and instead of just a single value I'm going to need to provide two values.
[09:45 - 09:55] So as soon as I provide two strings then it's satisfied. The type is satisfied.
[09:56 - 10:07] So let's just take out these favicon links and we'll do a quick check to see if our default values are also working throughout the hook. So I'm only going to set two titles.
[10:08 - 10:17] Hello world. And let's run the app and see if everything is still working as expected.
[10:18 - 10:21] And we've already got a good sign. We see our hello title.
[10:22 - 10:37] And of course if I lose focus here we get the toggling at approximately 500 milliseconds between the two titles. We don't get a favicon title of course because the default value is undefined so we don't expect any favicon swapping.
[10:38 - 10:52] And we do just get this looping effect as that also is the default value. So in summary we created a new type use please stay options to type the parameters of our hook.
[10:53 - 11:13] We added in two new utility types namely array of one or more and array of two or more which helped guard against either an empty titles array or a favicon links array which has less than two elements. In the actual use please stay implementation we added some sensible default values.
[11:14 - 11:34] And finally we documented all the parameters in this new type in the read me including their names, their types and their default value as well as a small description. In the next lesson we'll prepare a development only warning logger to help foreign developers if they pass potentially invalid parameters to the hook or other issues that they may encounter.