Fetching data with React Hooks
Tags: devjournal, javascript, react, reacthooks
Published: 2019-10-09 17:15:39

React Hooks are a relatively new feature of React that allows us to keep the state of our app in a function component, rather than having to create a class component. According to [React's documentation on Hooks](https://reactjs.org/docs/hooks-effect.html), the appropriate way to handle data fetching seems to be using the `useEffect` hook. So I'll try doing just that.

For the purposes of this post, I am going to imagine that I want to make an app that fetches a random book from an API, shows it as a book recommendation and allows the user to get a new book recommendation.

## Writing a `useEffect` hook

Writing a `useEffect` is relatively simple. We just need to create a function component and add some state to it.

```javascript
const BookApp = () => {
  const [ book, setBook ] = useState({})
  
  useEffect(() =>
    fetch("BookApiUrl")
      .then(res => res.json())
      .then(setBook)
  )
  
  return(
    <div>
      <img src={book.cover} alt={`${book.title}'s cover page`} />
      <p className="title">{book.title}</p>
      <p className="author">{book.author}</p>
    </div>
  )
}
```

Now, don't try this at home. If you do, you'll set an infinite loop. But don't worry, we'll fix that in a bit. This `useEffect` will execute when the component has finished mounting and every time something in the state changes. That's why we get an infinite loop. It fetches data from a fictional API, then updates the state, then the state change triggers another call to the `useEffect` and so on.

Other than that, there isn't much interesting going on. A normal `fetch` fetches data from an API. Then the data gets jsonified. Then we use the hook `setBook` to update the state.

## Stopping the infinite loop

The next thing to do is fixing that loop. `useEffect` takes two arguments. The first one is the function that it executes. The second one is an array of values. If these values haven't changed between re-renders, React will skip the effect.

```javascript
const BookApp = () => {
  const [ book, setBook ] = useState({})
  const [ count, setCount ] = useState(0)
  
  useEffect(() =>
    fetch("BookApiUrl")
      .then(res => res.json())
      .then(setBook),
  [count])
  
  return(
    <div>
      <img src={book.cover} alt={`${book.title}'s cover page`} />
      <p className="title">{book.title}</p>
      <p className="author">{book.author}</p>
      <button onClick={() => setCount(count + 1)}>Get another book</button>
    </div>
  )
}
```

This is definitely not a very elegant solution, but it is the first one I came with. I'll do something about it later. Now our `useEffect` will only execute if `count` changes between re-renderings. Other state changes will not trigger another data fetching. Since the state only changes when the user clicks on the button, that's the only thing that will trigger the effect (well, that and the component mounting).

> **Tip** if you want to mimic the behaviour of `componentDidMount`, you can simply add an empty array as the second argument. That will keep `useEffect` from executing on any state change.
> `useEffect(() => { do stuff }, [])`

## Making it more elegant

Having a state variable whose only purpose is to change when a button is clicked didn't strike me as a particularly good solution. As it stands now, there's another problem: the API might give us the same book twice in a row. Ideally we'd want to avoid that. So let's put both things together to make the code fancier.

Instead of having a counter that counts how many times the button has been clicked, I will store the previous book in the app's state and check that I don't get the same when I fetch the data.

```javascript
const BookApp = () => {
  const [ book, setBook ] = useState({})
  const [ lastBook, setLastBook ] = useState("")
  
  useEffect(() => {
    const fetchBook = () =>
      fetch("BookApiUrl")
        .then(res => res.json())
        .then(data => {
          if (data.title == lastBook) return fetchBook()
          else setBook(data)
        })
    fetchBook()
  }, [lastBook])
  
  return(
    <div>
      <img src={book.cover} alt={`${book.title}'s cover page`} />
      <p className="title">{book.title}</p>
      <p className="author">{book.author}</p>
      <button onClick={() => setLastBook(book.title)}>Get another book</button>
    </div>
  )
}
```

Now the state that tells the effect hook when to execute also serves the purpose of keeping track of the last book retrieved and avoiding retrieving it again. One change to note is that, since now the function that fetches the data is potentially recursive, it needs a name so it can call itself.

## Summary

If we need to fetch data from an API in a React app, we can place the fetching within an effect hook. We need to be careful not to set an infinite loop, though. We will need some way in the state to tell the hook when to execute. Other than that, it isn't much different than a normal `fetch`.