DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

useFetcher: the easiest way to fetch data in React

Introduction

The useFetcher hook is a React hook that makes it easy to fetch data from the server. It provides you with tons of features to facilitate your work with APIs specially if you're working on page that display lists with pagination, sorting or searching, this is your way to go with.

Installation

npm install @mongez/react-hooks
Enter fullscreen mode Exit fullscreen mode

OR

yarn add @mongez/react-hooks
Enter fullscreen mode Exit fullscreen mode

Usage

The useFetcher hook accepts a function that returns a promise, this function is your api request (axios) that receives list of params and returns a promise.

If you feel this article will be useful to you don't forget to share it with your friends and support me.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
    return axios.get('/api/users', { params });
};

const Users = () => {
    const { records, isLoading, error } = useFetcher(getUsers);

    if (isLoading) return <p>Loading...</p>;

    if (error) return <p>Something Went Wrong</p>;

    return (
        <div>
            {records.map(user => <p key={user.id}>{user.name}</p>)}
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

Pretty easy right? now let's see what else you can do with it.

Pagination

If the response of the api getUsers returns pagination details, then these details will be transformed into some properties you can use in your component, let's start with loadMore feature.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
    return axios.get('/api/users', { params });
};

const Users = () => {
    const { records, isLoading, error, loadMore } = useFetcher(getUsers);

    if (isLoading) return <p>Loading...</p>;

    if (error) return <p>Something Went Wrong</p>;

    return (
        <div>
            {records.map(user => <p key={user.id}>{user.name}</p>)}

            <button onClick={loadMore}>Load More</button>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

Now this will work if the response of the api returns pagination details, it will depend on the goToPage method that allow you to navigate to a specific page, let's see how to use it.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
    return axios.get('/api/users', { params });
};

const Users = () => {
    const { records, isLoading, error, goToPage } = useFetcher(getUsers);

    if (isLoading) return <p>Loading...</p>;

    if (error) return <p>Something Went Wrong</p>;

    return (
        <div>
            {records.map(user => <p key={user.id}>{user.name}</p>)}

            <button onClick={() => goToPage(2)}>Go To Page 2</button>
        </div>
    );
};
Enter fullscreen mode Exit fullscreen mode

This will work exactly as the previous example, but loadMore is useful if you're not working with pagination, for example when dealing lazy loading by scrolling or by the load more button.

The question here, how does the fetcher know the keys of the pagination, well there are predefined keys that you can use, but you can also change them to whatever you want, let's see how.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, goToPage } = useFetcher(getUsers, {
    keys: {
      records: "records",
      itemsPerPage: "meta.itemsPerPage",
      currentPage: "meta.currentPage",
      currentRecords: "meta.currentRecords",
      totalPages: "meta.totalPages",
      totalRecords: "meta.totalRecords",
      pageNumber: "page",
    },
  });

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      <button onClick={() => goToPage(2)}>Go To Page 2</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

As you can see, you can change the keys of the pagination to whatever you want, but you have to make sure that the response of the api returns the same keys as you specify them, here are the default keys that are being used.

const defaultOptions = {
  keys: {
    records: "records",
    itemsPerPage: "paginationInfo.itemsPerPage",
    currentPage: "paginationInfo.currentPage",
    currentRecords: "paginationInfo.currentRecords",
    totalPages: "paginationInfo.totalPages",
    totalRecords: "paginationInfo.totalRecords",
    pageNumber: "page",
  },
};
Enter fullscreen mode Exit fullscreen mode

The records key is the key that contains the list of records, the rest of the keys are used to calculate the pagination details.

Dot Notation is supported, so you can use paginationInfo.itemsPerPage to be taken from response.data object.

Override Default Fetch Options

Instead of modifying the keys of the pagination, you can also override the default fetch options, let's see how.

import { useFetcher, setFetchOptions } from '@mongez/react-hooks';

// somewhere in your app

setFetchOptions({
  keys: {
    records: "records",
    itemsPerPage: "meta.itemsPerPage",
    currentPage: "meta.currentPage",
    currentRecords: "meta.currentRecords",
    totalPages: "meta.totalPages",
    totalRecords: "meta.totalRecords",
    pageNumber: "page",
  },
});

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, goToPage } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      <button onClick={() => goToPage(2)}>Go To Page 2</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Knowing When To Fetch

There are some useful properties keys hat you can use as indicators whether to allow loading more data or even pagination or not, for instance using paginatable property you can know whether you should display the pagination list or the load more button.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, goToPage, paginatable } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      {paginatable && <button onClick={() => goToPage(2)}>Go To Page 2</button>}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Knowing the current page and total pages

You can also use currentPage and totalPages properties to know where you are now and how many pages you should display.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, goToPage, paginatable, currentPage, totalPages } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  const pages = [];

  for (let i = 1; i <= totalPages; i++) {
    pages.push(i);
  }

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      {paginatable && (
        <div>
          <button onClick={() => goToPage(currentPage - 1)}>Previous</button>
          {pages.map((page) => (
            <button key={page} onClick={() => goToPage(page)}>
              {page}
            </button>
          ))}
          <button onClick={() => goToPage(currentPage + 1)}>Next</button>
        </div>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

In the previous example, we combined all useful keys together, the totalPages to tell you how many pages you should display, the currentPage to tell you where you are now, and the paginatable to tell you whether you should display the pagination or not.

The First And last Page

You can also use isLastPage property to know whether you are on the last page or not, also isFirstPage to know whether you are on the first page or not.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, goToPage, paginatable, currentPage, totalPages, isFirstPage, isLastPage } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  const pages = [];

  for (let i = 1; i <= totalPages; i++) {
    pages.push(i);
  }

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      {paginatable && (
        <div>
        {! isFirstPage && <button onClick={() => goToPage(currentPage - 1)}>Previous</button>}
          {pages.map((page) => (
            <button key={page} onClick={() => goToPage(page)}>
              {page}
            </button>
          ))}
          {!isLastPage && <button onClick={() => goToPage(currentPage + 1)}>Next</button>}
        </div>
      )}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Default Params

You can also set default params that will be sent with every request using defaultParams property.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error } = useFetcher(getUsers, {
    defaultParams: {
      status: "active",
    },
  });

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

The defaultParams object will be merged with the params object that will be passed to the fetch method getUser on every request.

Load, Reset and Reload

Another useful feature of the useFetcher hook is the ability to load, reset and reload the data, the load method will allow you to customize your request params (apart from defaultParams), the reset method will recall the initial request which includes the default params data, and the reload method will recall the last request.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, load, reset, reload } = useFetcher(getUsers, {
    defaultParams: {
      status: "active",
    },
  });

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      <button onClick={() => load({ status: "inactive" })}>Load Inactive Users</button>
      <button onClick={() => reset()}>Reset</button>
      <button onClick={() => reload()}>Reload</button>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Current And Total Records

The last piece of information that you can get from the useFetcher hook is the currentRecords and totalRecords properties, the currentRecords will tell you how many records are currently loaded, and the totalRecords will tell you how many records are there in total.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, currentRecords, totalRecords } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      <p>
        Showing {currentRecords} of {totalRecords}
      </p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Entire Response

The useFetcher hook will return the entire response object from the request, so you can access any property from it using response property.

import { useFetcher } from '@mongez/react-hooks';

const getUsers = (params) => {
  return axios.get("/api/users", { params });
};

const Users = () => {
  const { records, isLoading, error, response } = useFetcher(getUsers);

  if (isLoading) return <p>Loading...</p>;

  if (error) return <p>Something Went Wrong</p>;

  return (
    <div>
      {records.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}

      <p>
        Showing {response.data.current_page} of {response.data.last_page}
      </p>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Bonus

If you want a simple hook with no pagination, you can use useRequest hook, it will return three properties only response isLoading and error.

Conclusion

I hope you enjoy this hook, @mongez/react-hooks has another useful hooks you can use but this will be in another night.

For more documentation check the Github repository.

Salam.

Latest comments (0)