Fetch data with React Hooks and Typescript
Tags: react, reacthooks, javascript, typescript
Published: 2019-03-26 13:03:46

---
title: Fetch data with React Hooks and Typescript
published: true
description: I show how to fetch data using the React Hooks `useState` and `useEffect`. I also show the code examples using Typescript.
tags: react, reacthooks, javascript, typescript
---

Reusing logic in React has been complex, and patterns like HOCs and Render Props tried to solve that problem. With the recent addition of Hooks, reusing logic becomes easier. In this article, I will show a simple way to use the Hooks `useEffect` and `useState` to load data from a web service (I'm using [swapi.co](https://swapi.co) in the examples to load Star Wars starships) and how to manage the loading state easily. As a bonus, I'm using Typescript. I will build a simple app to buy and sell Star Wars starships, you can see the final result here https://camilosw.github.io/react-hooks-services
 
# Loading initial data

Until the release of React Hooks, the simplest way to load initial data from a web service was on the `componentDidMount`:

```jsx
class Starships extends React.Component {
  state = {
    starships: [],
    loading: true,
    error: false
  }

  componentDidMount () {
    fetch('https://swapi.co/api/starships')
      .then(response => response.json())
      .then(response => this.setState({ 
        starships: response.results,
        loading: false
      }))
      .catch(error => this.setState({ 
        loading: false, 
        error: true 
      }));
  }

  render () {
    const { starships, loading, error } = this.state;
    return (
      <div>
        {loading && <div>Loading...</div>}
        {!loading && !error && 
          starships.map(starship => (
            <div key={starship.name}>
              {starship.name}
            </div>
          ))
        }
        {error && <div>Error message</div>}
      </div>
    );
  }
};

```
But reusing that code is hard because you can't extract behavior from the component prior React 16.8. The popular choices are to use higher-order components or render props, but there are some downsides with those approaches as described on the React Hooks documentation https://reactjs.org/docs/hooks-intro.html#its-hard-to-reuse-stateful-logic-between-components

With Hooks, we can extract the behavior to a custom Hook so we can reuse it in any component easily. If you don't know how to create custom Hooks, read the docs first: https://reactjs.org/docs/hooks-custom.html.

Because we are using Typescript, first we need to define the shape of the data we expect to receive from the web service, so I defined the interface `Starship`:

```js
export interface Starship {
  name: string;
  crew: string;
  passengers: string;
  cost_in_credits?: string;
  url: string;
}
```
And because we will be dealing with web services that have multiple states, I defined one interface per state. Finally, I defined `Service` as a union type of those interfaces:

```js
interface ServiceInit {
  status: 'init';
}
interface ServiceLoading {
  status: 'loading';
}
interface ServiceLoaded<T> {
  status: 'loaded';
  payload: T;
}
interface ServiceError {
  status: 'error';
  error: Error;
}
export type Service<T> =
  | ServiceInit
  | ServiceLoading
  | ServiceLoaded<T>
  | ServiceError;
```

`ServiceInit` and `ServiceLoading` define the state of the web service before any action and while loading respectively. `ServiceLoaded` has the property `payload` to store the data loaded from the web service (note that I'm using a generic here, so I can use that interface with any data type for the payload). `ServiceError` has the property `error` to store any error that may occur. With this union type, if we set the string `'loading'` in the `status` property and try to assign something to `payload` or `error` properties, Typescript will fail, because we didn't define an interface that allows a `status` of type `'loading'` alongside a property named `payload` or `error`. Without Typescript or any other type checking, your code will only fail at runtime if you make that mistake.

With the type `Service` and the interface `Starship` defined, now we can create the custom Hook `usePostStarshipService`:

```js
import { useEffect, useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

export interface Starships {
  results: Starship[];
}

const usePostStarshipService = () => {
  const [result, setResult] = useState<Service<Starships>>({
    status: 'loading'
  });

  useEffect(() => {
    fetch('https://swapi.co/api/starships')
      .then(response => response.json())
      .then(response => setResult({ status: 'loaded', payload: response }))
      .catch(error => setResult({ status: 'error', error }));
  }, []);

  return result;
};

export default usePostStarshipService;
```

This is what happens in the previous code:

* Because SWAPI returns an array of starships inside the array `results`, I defined a new interface `Starships` that contains the property `results` as an array of `Starship`.
* The custom Hook `usePostStarshipService` is simply a function, starting with the word `use` as recommended on the React Hooks documentation: https://reactjs.org/docs/hooks-custom.html#extracting-a-custom-hook.
* Inside that function, I'm using the Hook `useState` to manage the web service states. Note that I need to define the exact type of data that will be managed by the `result` state passing the generic `<Service<Starship>>`. I'm initializing the Hook with the interface `ServiceInit` of the union type `Service`, so the only property allowed is `status` with the string `'loading'`.
* I'm also using the Hook `useEffect` with a callback as the first argument to fetch the data from the web service, and an empty array as the second argument. That second argument tells `useEffect` what is the condition to run the callback, and because we are passing an empty array, the callback will be called only once (read more about `useEffect` if you are not familiarized with that Hook https://reactjs.org/docs/hooks-effect.html).
* Finally, I'm returning `result`. That object contains the state and any payload or error as a result of calling the web service. That's what we need in our component to show the status of the web service to the user and the data retrieved.

Note that the way I used `fetch` in the previous example is very simple but not enough for production code. For example, the catch will only capture network errors, not 4xx or 5xx errors. In your own code, it's better to create another function that wraps `fetch` for handling errors, headers, etc.

Now, we can use our Hook to retrieve the starships list and show them to the user:

```jsx
import React from 'react';
import useStarshipsService from '../services/useStarshipsService';

const Starships: React.FC<{}> = () => {
  const service = useStarshipsService();

  return (
    <div>
      {service.status === 'loading' && <div>Loading...</div>}
      {service.status === 'loaded' &&
        service.payload.results.map(starship => (
          <div key={starship.url}>{starship.name}</div>
        ))}
      {service.status === 'error' && (
        <div>Error, the backend moved to the dark side.</div>
      )}
    </div>
  );
};

export default Starships;

```

This time, our custom Hook `useStarshipService` will manage the status, so we only need to render conditionally based on the `status` property of the returned `service` object.

Note that if you try to access `payload` when status is `'loading'`,  TypeScript will fail, because `payload` only exists in the `ServiceLoaded` interface, not in the `ServiceLoading` one:

![](https://thepracticaldev.s3.amazonaws.com/i/zn5oo4w73c6tuevvfogq.png)

TypeScript is enough smart to know that if the comparison between the `status` property and the string `'loading'` is true, the corresponding interface is `ServiceLoaded` and in that circumstance the `starships` object doesn't have a `payload` property.


## Loading content on state change

In our example, if the user clicks on any starship, we change the state on our component to set the selected starship and call the web service with the url corresponding to that ship (note that https://swapi.co/api/starships loads all the data of every starship, so there is no need to load that data again. I'm doing that only for demonstration purposes.)

Traditionally we used componentDidUpdate to detect state changes and do something in consequence: 

```js
class Starship extends React.Component {
  ...

  componentDidUpdate(prevProps) {
    if (prevProps.starship.url !== this.props.starship.url) {
      fetch(this.props.starship.url)
        .then(response => response.json())
        .then(response => this.setState({
          starship: response,
          loading: false
        }))
        .catch(error => this.setState({ 
          loading: false, 
          error: true 
        }));
    }
  }
    
  ...
};
```

If we need to make different actions when different props and state properties change, `componentDidUpdate` quickly becomes a mess. With Hooks we can encapsulate that actions in separated custom Hooks. In this case, we'll create a custom Hook to extract the behavior inside `componentDidUpdate` as we did previously:

```js
import { useEffect, useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

const useStarshipByUrlService = (url: string) => {
  const [result, setResult] = useState<Service<Starship>>({
    status: 'loading'
  });

  useEffect(() => {
    if (url) {
      setResult({ status: 'loading' });
      fetch(url)
        .then(response => response.json())
        .then(response => setResult({ status: 'loaded', payload: response }))
        .catch(error => setResult({ status: 'error', error }));
    }
  }, [url]);

  return result;
};

export default useStarshipByUrlService;

```

This time, our custom Hook receives the url as a parameter, and we use that as the second argument of the Hook `useEffect`. That way, every time the url change, the callback inside `useEffect` will be called retrieving the data for the new starship.

Note that inside the callback, I'm calling `setResult` to set `status` as `'loading'`. That's because the callback will be called multiple times, so we need to reset the status before start fetching.

In our `Starship` component, we receive the url as a prop and pass it to our custom Hook `useStarshipByUrlService`. Every time the url change in the parent component, our custom Hook will call again the web service and will manage the status for us:

```jsx
import React from 'react';
import useStarshipByUrlService from '../services/useStarshipByUrlService';

export interface Props {
  url: string;
}

const Starship: React.FC<Props> = ({ url }) => {
  const service = useStarshipByUrlService(url);

  return (
    <div>
      {service.status === 'loading' && <div>Loading...</div>}
      {service.status === 'loaded' && (
        <div>
          <h2>{service.payload.name}</h2>
          ...
        </div>
      )}
      {service.status === 'error' && <div>Error message</div>}
    </div>
  );
};

export default Starship;

```


## Sending content

Sending content seems similar to loading content when state change. In the first case we passed a url to our custom Hook and now we could pass an object with the data to be sent. If we try to do the same, the code will be something like this:

```jsx
const usePostStarshipService = (starship: Starship) => {
  const [result, setResult] = useState<Service<Starship>>({
    status: 'init'
  });

  useEffect(() => {
    setResult({ status: 'loading' });
    fetch('https://swapi.co/api/starships', {
      method: 'POST',
      body: JSON.stringify(starship)
    })
      .then(response => response.json())
      .then(response => {
        setResult({ status: 'loaded', payload: response });
      })
      .catch(error => {
        setResult({ status: 'error', error });
      });
  }, [starship]);

  return result;
};

const CreateStarship: React.FC<{}> = () => {
  const initialStarshipState: Starship = {
    name: '',
    crew: '',
    passengers: '',
    cost_in_credits: ''
  };
  const [starship, setStarship] = useState<PostStarship>(initialStarshipState);
  const [submit, setSubmit] = useState(false);
  const service = usePostStarshipService(starship);

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    event.persist();
    setStarship(prevStarship => ({
      ...prevStarship,
      [event.target.name]: event.target.value
    }));
  };

  const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    setSubmit(true);
  };

  useEffect(() => {
    if (submit && service.status === 'loaded') {
      setSubmit(false);
      setStarship(initialStarshipState);
    }
  }, [submit]);

  return (
    <form onSubmit={handleFormSubmit}>
      <input 
        type="text" 
        name="name" 
        value={starship.name} 
        onChange={handleChange}
      />
      ...
    </form>
  )
}
```
But there are some problems on the previous code:
* We passed the `starship` object to our custom Hook and we passed that object as the second argument of the `useEffect` Hook. Because the onChange handler will change the `starship` object on every keystroke, our web service will be called every time the user type.
* We need to use the Hook `useState` to create the boolean state `submit` only to know when we can clean the form. We could use this boolean as the second parameter of `usePostStarshipService` to solve the previous problem, but we'd be complicating our code.
* The boolean state `submit` added logic to our component that must be replicated on other components that reuse our custom Hook `usePostStarshipService`.

There is a better way, this time without the `useEffect` Hook:

```js
import { useState } from 'react';
import { Service } from '../types/Service';
import { Starship } from '../types/Starship';

export type PostStarship = Pick<
  Starship,
  'name' | 'crew' | 'passengers' | 'cost_in_credits'
>;

const usePostStarshipService = () => {
  const [service, setService] = useState<Service<PostStarship>>({
    status: 'init'
  });

  const publishStarship = (starship: PostStarship) => {
    setService({ status: 'loading' });

    const headers = new Headers();
    headers.append('Content-Type', 'application/json; charset=utf-8');

    return new Promise((resolve, reject) => {
      fetch('https://swapi.co/api/starships', {
        method: 'POST',
        body: JSON.stringify(starship),
        headers
      })
        .then(response => response.json())
        .then(response => {
          setService({ status: 'loaded', payload: response });
          resolve(response);
        })
        .catch(error => {
          setService({ status: 'error', error });
          reject(error);
        });
    });
  };

  return {
    service,
    publishStarship
  };
};

export default usePostStarshipService;

```
First, we created a new `PostStarship` type derived from `Starship`, picking the properties that will be sent to the web service. Inside our custom Hook, we initialized the service with the string `'init'` in the property `status` because `usePostStarshipService` will do nothing with the web service when called. Instead of the `useEffect` Hook, this time we created a function that will receive the form data to be sent to the web service and will return a Promise. Finally, we return an object with the `service` object and the function in charge to call the web service.

**Note:** I could have returned an array instead of an object in our custom Hook to behave like the `useState` Hook, that way the names in the component could be named arbitrarily. I decided to return an object instead because I think there is no need to rename them. You are free to return an array instead if you prefer.

Our `CreateStarship` component will be simpler this time:

```jsx
import React, { useState } from 'react';
import usePostStarshipService, {
  PostStarship
} from '../services/usePostStarshipService';
import Loader from './Loader';

const CreateStarship: React.FC<{}> = () => {
  const initialStarshipState: PostStarship = {
    name: '',
    crew: '',
    passengers: '',
    cost_in_credits: ''
  };
  const [starship, setStarship] = useState<PostStarship>(
    initialStarshipState
  );
  const { service, publishStarship } = usePostStarshipService();

  const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    event.persist();
    setStarship(prevStarship => ({
      ...prevStarship,
      [event.target.name]: event.target.value
    }));
  };

  const handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
    event.preventDefault();
    publishStarship(starship).then(() => setStarship(initialStarshipState));
  };

  return (
    <div>
      <form onSubmit={handleFormSubmit}>
        <input
          type="text"
          name="name"
          value={starship.name}
          onChange={handleChange}
        />
        ...
      </form>
      {service.status === 'loading' && <div>Sending...</div>}
      {service.status === 'loaded' && <div>Starship submitted</div>}
      {service.status === 'error' && <div>Error message</div>}
    </div>
  );
};

export default CreateStarship;

```
I'm using the `useState` Hook to manage the state of the form but `handleChange` behaves as when we use `this.state` in class components. Our `usePostStarshipService` does nothing other than returning our `service` object in an initial state and returning the publishStarship method to call the web service. When the form is submitted and `handleFormSubmit` is called, we call `publishStarship` with the form data. Now our `service` object starts to manage the state of the web service changes. If the returned promise is success, we call `setStarship` with the `initialStarshipState` to clean the form.

And that's all, we have three custom Hooks to retrieve initial data, retrieve individual items and post data. You can see the full project here: https://github.com/camilosw/react-hooks-services

# Final Thoughts

React Hooks are a great addition but don't try to overuse them when there are simpler and well-established solutions, like the Promise instead of `useEffect` on our sending content example.

There is another benefit when using Hooks. If you look closer, you will see that our components became basically presentational, because we moved the stateful logic to our custom Hooks. There is an established pattern to separate logic from presentation, called container/presentational, where you put the logic in a parent component and presentation in children components. That pattern was initially ideated by Dan Abramov, but now that we have Hooks, Dan Abramov advises to use less that pattern in favor of using Hooks: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

Probably you hate using strings to name states and blamed me for doing that, but if you are using Typescript, you are safe, because Typescript will fail if you misspell the state name and you will get autocomplete for free in VS Code (and other editors probably). Anyway, you can use booleans if you prefer.