DEV Community

loading...
Cover image for React Hooks for Data Part 1 - Fetching Data

React Hooks for Data Part 1 - Fetching Data

bdbch profile image Dominik Biedebach ・2 min read

So the large hype on React Hooks is over and the community isn't talking about them that much anymore. But seriously hooks are beasts. In this post I'll explain how you can use React Hooks to fetch and submit data to any API (I'll use REST in this guide).

Writing your own hook

We'll start with writing our first hook to fetch books from an API. Here is the example code:

import { useEffect, useState } from 'react'

// The hook is just a simple function which we can export
export const useFetchBooks = () => {

  // First we define the necessary states for our hook
  // this includes book, the loading state and potential errors
  const [books, setBooks] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  // useEffect can be compared to componentDidMount,
  // componentDidUpdate and componentDidUnmount
  // read more about useEffect here:
  // https://reactjs.org/docs/hooks-effect.html
  useEffect(() => {

    // First we set the loading and error states
    setLoading(true)
    setError(null)

    fetch('https://library.com/api/books')
      .then(res => res.json())
      .then(json => {
        setLoading(false)
        if (json.books) {
          setBooks(json.books)
        } else {
          setBooks([])
        }
      })
      .catch(err => {
        setError(err)
        setLoading(false)
      })
  }, [])
  return { books, loading, error }
}

Now this looks complicated but really it isn't. Remove the comments and it will be a really short function which fetches data and updates states.

Now that we have the hook, we can use it in a component like this:

import React from 'react'
import { useFetchBooks } from './utils/hooks'

const BookList = () => {
  // use your own hook to load the data you need
  const { books, loading, error } = useFetchBooks()

  if (loading) return <div>Loading...</div>
  if (error) return <div>{error}</div>

  return (
    <div>
      { 
        books &&
        books.length > 0 &&
        books.map(book => <div key={book.id}>{book.title}</div>)
      } 
    </div>
  )
}

export default BookList

Use parameters in your hook

Now our hook works fine but it's still a bit stupid. Lets say you want your users to be able to search for books in the list. You could do it like this:

import { useEffect, useState } from 'react'

// Note here the new parameter we pass into the hook called "search"
// this will be used to search the api for specific books
export const useFetchBooks = (search) => {
  const [books, setBooks] = useState([])
  const [loading, setLoading] = useState(false)
  const [error, setError] = useState(null)

  useEffect(() => {
    setLoading(true)
    setError(null)

    // Change the apiUrl according to the search string
    const apiUrl = search && search.length > 0 ?
      `https://library.com/api/books?search=${search}` :
      'https://library.com/api/books'

    fetch(apiUrl)
      .then(res => res.json())
      .then(json => {
        setLoading(false)
        if (json.books) {
          setBooks(json.books)
        } else {
          setBooks([])
        }
      })
      .catch(err => {
        setError(err)
        setLoading(false)
      })

  // This is important. We pass the new search parameter into
  // the empty array we had before. This means, the effect
  // will run again if this parameter changes
  }, [search])

  return { books, loading, error }
}

Now you can use the hook like this in your component:

const { books, loading, error } = useFetchBooks(props.search)

This should be enough for part 1 and should clarify how to use hooks to fetch data from any API.

I'll update this post with a link to part 2 as soon as I'm done with it.

Have fun!

Discussion

pic
Editor guide
Collapse
penspinner profile image
Steven Liao

I had recently implemented something similar to this.

const [loading, setLoading] = useState(false)

I did the same thing, initializing loading state to false. However, my coworkers argued that it could be

const [loading, setLoading] = useState(true)

because the Hook will eventually set loading state to true any way. I argued that the Hook isn't actually fetching until the effect runs, which is why I initialized loading state to false. I eventually gave in and initialized loading state to true because I felt like we were bikeshedding.

Anyway, what do you think?

Collapse
penspinner profile image
Steven Liao

Eventually found one good thing about initializing loading state to true. If the component using this custom fetch Hook rendered some other content, it won't flicker. In your case, this shouldn't happen though since BookList isn't rendering anything other than the fetched books.

Collapse
bdbch profile image
Dominik Biedebach Author

That's true! I'll update the post and add your note to it.

Collapse
webcoderkz profile image
Alexander Kim

How do you deal with this part:

if (loading) return <div>Loading...</div>

This causes annoying flashing, before promise is resolved. Is there a way to show this loader inside actual markup, instead of this single element with Loading...?

Collapse
monfernape profile image
Usman Khalil

Loved your explanation. Can you describe useEffect in detail in next post. I couldn't understand the use case

Collapse
bdbch profile image
Dominik Biedebach Author

Hey, thank you a lot.

I won't write about useEffect in the next post but I just wrote a quick explanation here:
dev.to/bdbch/a-quick-explanation-o...

Let me know if it helped you! :)

Collapse
accrepo1 profile image
acc-repo1

Nice Article...

useful info, remarkable clarity, one small typo (closing paren):

    books.map(book => <div key={book.id}>{book.title}</div>);
Collapse
bdbch profile image
Dominik Biedebach Author

Thank you very much! Fixed!

Collapse
bdbch profile image
Dominik Biedebach Author

I'd say because of the comments which are cluttering the actual source code and the new way to update states etc.

Collapse
sebastienlorber profile image
Sebastien Lorber

This code is subject to race conditions. Check my post about that subject here: dev.to/sebastienlorber/handling-ap...

Collapse
kimsean profile image
Kim Sean Pusod

what would be the pros and cons on using fetch vs axios ?