Custom hooks react typescript

TypeScript + React: Typing custom hooks with tuple types

Stefan Baumgartner

I recently stumbled upon a question on Reddit’s LearnTypeScript subreddit regarding custom React hooks. A user wanted to create a toggle custom hook, and stick to the naming convention as regular React hooks do: Returning an array that you destructure when calling the hook. For example useState :

const [state, setState] = useState(0)

Why an array? Because you the array’s fields have no name, and you can set names on your own:

const [count, setCount] = useState(0)
const [darkMode, setDarkMode] = useState(true)

So naturally, if you have a similar pattern, you also want to return an array.

A custom toggle hook might look like this:

export const useToggle = (initialValue: boolean) =>  
const [value, setValue] = useState(initialValue)
const toggleValue = () => setValue(!value)
return [value, toggleValue]
>

Nothing out of the ordinary. The only types we have to set are the types of our input parameters. Let’s try to use it:

export const Body = () =>  
const [isVisible, toggleVisible] = useToggle(false)
return (
>
/* It very much booms here! 💥 */ >
button onClick=toggleVisible>>Hello/button>
isVisible && div>World/div>>
/>
)
>

So why does this fail? TypeScript’s error message is very elaborate on this: Type ‘boolean | (() => void)’ is not assignable to type ‘((event: MouseEvent) => void) | undefined’. Type ‘false’ is not assignable to type ‘((event: MouseEvent) => void) | undefined’.

It might be very cryptic. But what we should look out for is the first type, which is declared incompatible: boolean | (() => void)’ . This comes from returning an array. An array is a list of any length, that can hold as many elements as virtually possible. From the return value in useToggle , TypeScript infers an array type. Since the type of value is boolean (great!) and the type of toggleValue is (() => void) (a function returning nothing), TypeScript tells us that both types are possible in this array.

And this is what breaks the compatibility with onClick . onClick expects a function. Good, toggleValue (or toggleVisible ) is a function. But according to TypeScript, it can also be a boolean! Boom! TypeScript tells you to be explicit, or at least do type checks.

But we shouldn’t need to do extra type-checks. Our code is very clear. It’s the types that are wrong. Because we’re not dealing with an array.

Let’s go for a different name: Tuple. While an array is a list of values that can be of any length, we know exactly how many values we get in a tuple. Usually, we also know the type of each element in a tuple.

So we shouldn’t return an array, but a tuple at useToggle . The problem: In JavaScript an array and a tuple are indistinguishable. In TypeScript’s type system, we can distinguish them.

Option 1: Add a return tuple type #

First possibility: Let’s be intentional with our return type. Since TypeScript – correctly! – infers an array, we have to tell TypeScript that we are expecting a tuple.

// add a return type here
export const useToggle =
(initialValue: boolean): [boolean, () => void] =>
const [value, setValue] = useState(initialValue)
const toggleValue = () => setValue(!value)
return [value, toggleValue]
>

With [boolean, () => void] as a return type, TypeScript checks that we are returning a tuple in this function. TypeScript does not infer anymore, but rather makes sure that your intended return type is matched by the actual values. And voila, your code doesn’t throw errors anymore.

Option 2: as const #

With a tuple, we know how many elements we are expecting, and know the type of these elements. This sounds like a job for freezing the type with a const assertion.

export const useToggle = (initialValue: boolean) =>  
const [value, setValue] = useState(initialValue)
const toggleValue = () => setValue(!value)
// here, we freeze the array to a tuple
return [value, toggleValue] as const
>

The return type is now readonly [boolean, () => void] , because as const makes sure that your values are constant, and not changeable. This type is a little bit different semantically, but in reality, you wouldn’t be able to change the values you return outside of useToggle . So being readonly would be slightly more correct.

And this is, a perfect use case for tuple types! As always, there’s a playground link for you to fiddle around! Have fun!

Cover of Front-End Tooling

I’ve written a book on TypeScript! Check out TypeScript in 50 Lessons, published by Smashing Magazine

Источник

How to create a custom React hook to fetch an API (using TypeScript)?

Hooks are convenient for modern react development. The react framework comes with standard hooks to manage state, for example, with useState, and here we will write our hook to fetch data from any API. Buț first …

… what is a hook?

A hook is a javascript or typescript function that can include other hooks. Its name starts with « use », and this function can only be called inside a React functional component. You can find the complete Rules of Hooks documentation here.

Let’s start

First, create a new React project using Typescript.
In the terminal, navigate to the desired folder, and with the terminal command :
npx create-react-app apihook —template typescript The project is ready, time to think about the output of our hook to set the goal.

The output

  • response status code: to test the response code
  • response status text: to get the response status in a more readable way
  • data: data provided by the API
  • error: description of the error if one occurs
  • loading: to know if the process is running

We will write a type to set that!

Coding!

I will create a new folder to store my hook and a new file named useApiHook.ts

Image description

And set my type as following :

We will now declare my hook as a function that will take a string containing the url as parameter and return a TApiResponse :

export type TApiResponse = < status: Number; statusText: String; data: any; error: any; loading: Boolean; >; export const useApiGet = (url: string): TApiResponse => <>; 

We will also use the state to store the information before returning the response. For this purpose, we will use a standard hook named useState, and import this function from the React framework :

import < useState >from 'react'; export type TApiResponse = < status: Number; statusText: String; data: any; error: any; loading: Boolean; >; export const useApiGet = (url: string): TApiResponse => < const [status, setStatus] = useState(0); const [statusText, setStatusText] = useState(''); const [data, setData] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); >; 

Please note that we initialize status and textStatus to avoid « undefined ». If not, we would get a TypeScript error telling that it doesn’t match the type we defined (the power of TypeScript !).

Time to get the data!
Here we will use an async function to create a promise and get the data. We will also use try/catch to catch an error if something wrong happens.
We also set isLoading to ‘true’, so the process will be set as running :

import < useState >from 'react'; export type TApiResponse = < status: Number; statusText: String; data: any; error: any; loading: Boolean; >; export const useApiGet = (url: string): TApiResponse => < const [status, setStatus] = useState(0); const [statusText, setStatusText] = useState(''); const [data, setData] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const getAPIData = async () => < setLoading(true); try < const apiResponse = await fetch(url); const json = await apiResponse.json(); >catch (error) < >>; >; 

We are almost done !
Now let’s store the results in the different states, and at the end, set isLoading to false to declare that the process is finished:

import < useState >from 'react'; export type TApiResponse = < status: Number; statusText: String; data: any; error: any; loading: Boolean; >; export const useApiGet = (url: string): TApiResponse => < const [status, setStatus] = useState(0); const [statusText, setStatusText] = useState(''); const [data, setData] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const getAPIData = async () => < setLoading(true); try < const apiResponse = await fetch(url); const json = await apiResponse.json(); setStatus(apiResponse.status); setStatusText(apiResponse.statusText); setData(json); >catch (error) < setError(error); >setLoading(false); >; >; 

To finish our custom hook, we need to trigger the function we have crated. To do so, we use another standard hook : useEffect().
This hook will execute code when the component loads or some variable has changed.
We will only use it when the component is loaded for our purpose.
We need first to import it and use it to call our function :

import < useState, useEffect >from 'react'; export type TApiResponse = < status: Number; statusText: String; data: any; error: any; loading: Boolean; >; export const useApiGet = (url: string): TApiResponse => < const [status, setStatus] = useState(0); const [statusText, setStatusText] = useState(''); const [data, setData] = useState(); const [error, setError] = useState(); const [loading, setLoading] = useState(false); const getAPIData = async () => < setLoading(true); try < const apiResponse = await fetch(url); const json = await apiResponse.json(); setStatus(apiResponse.status); setStatusText(apiResponse.statusText); setData(json); >catch (error) < setError(error); >setLoading(false); >; useEffect(() => < getAPIData(); >, []); return < status, statusText, data, error, loading >; >; 

Now that our hook is done let’s call it in the main application.

Use the custom hook

In our example, we will call the hook to fetch a movie database API and console.log the result.
We need to create an account on omdbapi.com to get a free API key required to pull the data.

In the file App.tsx, we will :

  • import the type and the custom hook
  • add the call to the API and store the result in a variable called data

Then to display the result, I will use the property loading from the response to avoid multiple print during the process:

import React from 'react'; import logo from './logo.svg'; import './App.css'; import < useApiGet, TApiResponse >from './hooks/useApiHook'; function App() < // call to the hook const data: TApiResponse = useApiGet( 'http://www.omdbapi.com/?s=Guardians&apikey=xxxxxxxx' ); // print the output if (!data.loading) console.log(data); return ( 
className="App-logo" alt="logo" />

Edit src/App.tsx and save to reload.

Learn React
); > export default App;

Run the app

Finally let’s run the app by typing in the console :
npm start

Image description

Conclusion

Hooks can be super handy and allow the creation of reusable functions. They have to follow some rules to build them and are very flexible.
For our example, we could go further and extend the function to handle parameters, other methods, some checks and controls, but I wanted to keep it simple to explain the principle.

Now I invite you to create custom hooks for your react apps, and feel free to share some usages in the comments.

Article also available on Medium

Источник

Читайте также:  Php get content uploaded file
Оцените статью