DEV Community

Stéphane Sulikowski
Stéphane Sulikowski

Posted on

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

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

Our hook will fetch an API, and the function will return a result.
For this example, we want to get the following information:

  • 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 :

export type TApiResponse = {
  status: Number;
  statusText: String;
  data: any;
  error: any;
  loading: Boolean;
};
Enter fullscreen mode Exit fullscreen mode

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 => {};
Enter fullscreen mode Exit fullscreen mode

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<Number>(0);
  const [statusText, setStatusText] = useState<String>('');
  const [data, setData] = useState<any>();
  const [error, setError] = useState<any>();
  const [loading, setLoading] = useState<boolean>(false);
};
Enter fullscreen mode Exit fullscreen mode

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<Number>(0);
  const [statusText, setStatusText] = useState<String>('');
  const [data, setData] = useState<any>();
  const [error, setError] = useState<any>();
  const [loading, setLoading] = useState<boolean>(false);

  const getAPIData = async () => {
    setLoading(true);
    try {
      const apiResponse = await fetch(url);
      const json = await apiResponse.json();
    } catch (error) {
    }
  };
};
Enter fullscreen mode Exit fullscreen mode

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<Number>(0);
  const [statusText, setStatusText] = useState<String>('');
  const [data, setData] = useState<any>();
  const [error, setError] = useState<any>();
  const [loading, setLoading] = useState<boolean>(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);
  };
};
Enter fullscreen mode Exit fullscreen mode

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<Number>(0);
  const [statusText, setStatusText] = useState<String>('');
  const [data, setData] = useState<any>();
  const [error, setError] = useState<any>();
  const [loading, setLoading] = useState<boolean>(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 };
};
Enter fullscreen mode Exit fullscreen mode

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 loadingfrom 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 (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer">
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Run the app

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

And …

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.

Happy coding!

Article also available on Medium

Latest comments (5)

Collapse
 
arjun259194 profile image
Arjun259194

I want to fetch data every time a value changes but i can't call this hook inside useEffect(), what should i do?

Collapse
 
brian1150 profile image
Brian-1150

@sulistef Great article. Thanks for sharing!

Collapse
 
elyasthr profile image
Elyas

what is the use of coding with ts to put any at data, precisely I would like to know what to put in place of any so that it works in strict mode

Collapse
 
brian1150 profile image
Brian-1150

@elyas This hook is meant to be used by reused by various components or functions. By design, it can accept any url as an argument and return any data that gets returned from that API. If you know what kind of data you are expecting to get back, you can make an interface specific for that component.

In this example we know we are expecting to get back movie info and we can adjust the App component like this:

import React, { useEffect, useState } from 'react'
import { useApiGet, TApiResponse } from '../hooks/useApiHook'

interface Movie {
    Released: string;
    Response: string;
    Runtime: string;
    Title: string;
    Type: string;
    Website: string;
    Writer: string;
    Year: string;
    imdbID: string;
    imdbRating: string;
    imdbVotes: string;
    Actors: string;
    Awards: string;
    BoxOffice: string;
    Country: string;
    DVD: string;
    Director: string;
    Genre: string;
    Language: string;
    Metascore: string;
    Plot: string;
    Poster: string;
    Production: string;
    Rated: string;
}
function Test() {
    const [data, dataSet] = useState<Movie>({} as Movie);
    const testData: TApiResponse = useApiGet(
        // 'http://www.omdbapi.com/?s=Guardians&apikey=xxxxxxxx'
        'http://www.omdbapi.com/?t=GoodFellas&apikey=1fcaeb08'
    )

    useEffect(() => {
        if (testData) {
            dataSet((testData.data) as Movie);
        }

    }, [testData])
    console.log(data);

    return (
        <div>
            {data && <div>{data.Title} was released in {data.Year}</div>}
        </div>
    );
}

export default Test;
Enter fullscreen mode Exit fullscreen mode

The same thing can be done for any API returning "any" kind of data.

Collapse
 
dzienisz profile image
Kamil Dzieniszewski

setData not dataSet