Website logo

06 Aug 2023

Fetching
Data
with
React
Query

In a website, usually we need to get certain data from the server then we will show it on the web page, product list for example. Data that comes from the server we called it as server state. In React, we usually use axios to create a request to the server to get data in the response and then that data we save to the state like this.

const ProductListPage = () => {
  const [products, setProducts] = useState([])
  const [isLoading, setIsLoading] = useState(false)

  useEffect(() => {
    const fetchProducts = async () => {
      try {
        setIsLoading(true)

        const response = await axios.get('/products')

        setProducts(response)
      } catch (error) {
        console.log(error)
      } finally {
        setIsLoading(false)
      }
    }

    fetchProducts()
  }, [])

  return (
    <div>
      {!isLoading && (
        // Show product list here.
      )}
      {isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

Those code we used to get all products and then we can show it to the web page. But, there are another cases regarding data-fetching that we need to pay attention.

  • How if we have products data too much, around 100,000 items for example? We can implement pagination for product list by showing 20 items per page.
  • What should we do if there's an error when fetching products? We can show the error in the product list section in that corresponding web page.
  • How we can recover from that error? We can try to refetching the data if the error has been fixed on the server.
  • How we can fetch the products from page A then later we can show it on the page B? We can try to fetching products from page A then later we can save the response to the global state, then we can show it on the page B.
  • How we can get latest data from the server? We can refetching when needed from any web pages.

Those are parts of many other cases that might happen regarding data-fetching. To implement those kind of things, is not easy and need so much effort. We need to think about where we should define the state, we need to think about how data from pagination should be saved, and think about other troublesome things. Usually in a website, there are many variants of data that need to be requested to the server that later we can show it on the web page, not only product list. It's really painful.

Solution

With using React Query, we can doing data-fetching better and solve those kinds of problems. There are some examples to fetching data with React Query. First we can create a custom hook in a file, for example use-products-query.js. Here ['products', payload] is a query key, it's like a unique identity for a query, so if the query key changes so the query will doing data-fetching and if the data successfully fetched, then isLoading will change from true to false. And if a query from a query key is already doing data-fetching or already has initial data, then isLoading will always be false because React Query later will doing data-fetching in the background for that query key. That's why we can also use isFetching.

const action = (payload) => {
  const response = await axios.get('/products', {
    params: payload
  })

  return response
}

const useProductsQuery = (payload) => {
  return useQuery(['products', payload], () => action(payload))
}

Then later we can use our custom hook that we already created in the any pages or components that need it.

const ProductListPage = () => {
  const productsQuery = useProductsQuery()

  return (
    <div>
      {productsQuery.isSuccess && (
        // Show product list here.
      )}
      {productsQuery.isError && (
        // Show error here.
      )}
      {productsQuery.isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

We can also implement the filter for the product list, so every time category changed, then the query will doing data-fetching to filter the products.

const ProductListPage = () => {
  const [category, setCategory] = useState("gadget");
  const productsQuery = useProductsQuery({ category });

  // Other code here.
};

For example, there's an error happen when fetching the data, then we can show the error message and a button that we can use to recover from that error if the error on the server has been fixed. So when we click that button, the query will be refetching the data and show the loading.

const ProductListPage = () => {
  // Other code here.

  return (
    <div>
      {productsQuery.isSuccess && (
        // Show product list here.
      )}
      {productsQuery.isError && (
        <div>
          <p>Products cannot be shown.</p>
          <button
            type="button"
            onClick={() => productsQuery.refetch()}
          >
            Try Again
          </button>
        </div>
      )}
      {productsQuery.isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

We can also if we need to do data-fetching for page B from page A for example. Page B will be show product list data that already fetched in page A (cached data), but page B will still doing data-fetching of product list data in the background to keep in sync with the latest data. Here loading might be not appears on the page B because product list is already fetched in page A.

const APage = () => {
  const productsQuery = useProductsQuery()

  return (
    <div>
      <button
        type="button"
        onClick={() => productsQuery.refetch()}
      >
        Fetch product list.
      </button>
    </div>
  )
}

const BPage = () => {
  const productsQuery = useProductsQuery()

  return (
    <div>
      {productsQuery.isSuccess && (
        // Show product list here.
      )}
      {productsQuery.isError && (
        // Show error here.
      )}
      {productsQuery.isLoading && (
        // Show loading here.
      )}
    </div>
  )
}

We can also implement pagination with ease. Previously, please make sure that REST API /products to supports pagination as well. Code below will be used to get product list with category gadget on the page 1.

const ProductListPage = () => {
  const [category, setCategory] = useState("gadget");
  const [page, setPage] = useState(1);
  const productsQuery = useProductsQuery({ category, page });

  return (
    <div>
      {productsQuery.isSuccess && (
        <>
          {/* Show product list here. */}

          <Pagination
            currentPage={page}
            totalPages={productsQuery.data.last_page}
            onPageChange={(pageIndex) => {
              setPage(pageIndex)
            }}
          />
        </>
      )}
      {productsQuery.isError && (
        // Show error here.
      )}
      {productsQuery.isLoading && (
        // Show loading here.
      )}
    </div>
  )
};

Conclusion

With using React Query, data-fetching implementation will be easy, less effort, cleaner code and easier to read, and definitely will make us more productive. It will be hard at start, but believe me, React Query is really useful and worth to invest your time for learning it.