Writing Custom React Hooks with Typescript
Nick Scialli
September 19, 2020
Writing custom React hooks with Typescript isn’t all that different from writing them in plain old JavaScript. In this post, we review some basics and “gotchas” of writing your own React hooks with Typescript.
Example: A Simple useFetch Hook
To demonstrate how to write our hooks, we’ll write a hook that does something pretty ubiquitous in web applications: fetching data. Our hook will be called useFetch
.
Importantly, we’re just going through the basics here, so this will be a very bare-bones hook. If you want to actually use a useFetch
hook in production, I recommend googling around for an existing one that has a lot more bells and whistles.
Making GET Requests
To make a get request with our hook, we know we need a URL, and we need to return some fetched data. Our useFetch
hook, therefore, will have a url
parameter that’s a string
, and will use the useState
hook built-in to react store our fetched data.
import { useState } from 'react';
function useFetch(url: string) {
const [data, setData] = useState(null);
// Fetch the data here
return data;
}
Now the fun part: let’s fetch and set our data! We can simply use our window’s fetch
method and setData
to the response.
import { useState } from 'react';
function useFetch(url: string) {
const [data, setData] = useState(null);
// Fetch the data here
fetch(url)
.then((res) => {
return res.json();
})
.then((res) => {
setData(res);
});
return data;
}
Let’s see this in action by using our hook in an app.
import { useFetch } from './useFetch';
function App() {
const data = useFetch('https://api.github.com/users/nas5w');
return data ? <>{JSON.stringify(data)}</> : <>Loading...</>;
}
export default App;
And this works: We’ll see a Loading...
message while our fetch is happening and then our stringified github data will display.
But this isn’t good enough: our application thinks the type of data
is null
since that’s the only type we explicitly gave our stateful data
variable in our hook.
Not Good Enough! We Need to Type the Return Value
To allow ourselves to type the return value from our useFetch
hook, we can use Typescript generics to pass a type to our hook. Let’s see how this would look.
import { useState } from 'react';
function useFetch<D>(url: string) {
const [data, setData] = useState<D | null>(null);
// Fetch the data here
fetch(url)
.then((res) => {
return res.json();
})
.then((res) => {
setData(res);
});
return data;
}
So now we’ve told our hook that the hook must be provided a type, D
, and the type of data
will be D
or null
. We can now revisit our App.tsx
file and adjust how we use this hook.
For this exercise, I have created a basic GithubResponse
type. It’s probably not 100% correct, but good enough for this demonstration!
import { useFetch } from './useFetch';
function App() {
const data = useFetch<GithubResponse>('https://api.github.com/users/nas5w');
return data ? (
<>
Name: {data.login}
<br />
Followers: {data.followers}
</>
) : (
<>Loading...</>
);
}
export default App;
type GithubResponse = {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
name: string;
company: string | null;
blog: string | null;
location: string | null;
email: string | null;
hireable: string | null;
bio: string;
twitter_username: string | null;
public_repos: number;
public_gists: number;
followers: number;
following: number;
created_at: string;
updated_at: string;
};
As we can see, our code compiles, meaning Typescript understands our returned object type!
TL;DR: Generics Make Your Hooks Work
The TL;DR here is that Typescript generics are key to making your React Hooks work in Typescript!
Nick Scialli is a senior UI engineer at Microsoft.