Building Your Own Hook for fetching data from database.

·

4 min read

Building Your Own Hook for fetching data from database.

This example demonstrates creating a custom hook to fetch data from the database, providing parameters such as isLoading, isRefreshing, noMoreData, total amount of records, and the result.

When you build data-driven applications a lot you may catch your self by doing a lot of repeatable and tedious work. Some developers tend to have an application logic separated into separate files and do fetching data calls from there. For more sophisticated scenarios they use Redux, Saga, and Context techniques. This may be a good reason to do so, but in more cases when we are talking about just how to fetch the data and represent it into our application we use simple hooks like use-http.

Nevertheless, we’ve put more synthetic sugar when we are talking about work with API. In this example, we will show you how to create a custom hook, that fetches data from the database and outputs various parameters like isLoading, isRefreshing, noMoreData, totalAmount of records, and result.

This custom hook is useful when you want to have a reusable component. As a result, it gives you less code to write and that means your code is less error-prone. Also, it gives you the capability to work hand in hand with a component like list. So when you pass parameters from your list, it automatically works with hook and passes data back and forth. As an example, you can check out our blog post to get more feeling of how it works with the custom list in React Native project.

Custom Hook that fetches data

/* eslint-disable no-shadow */
import { useEffect, useState } from 'react';
import { db } from 'codemash';
import { PROJECT_CONFIG } from '../config/ProjectConfig';

export default ({ collectionName, filter, pageSize, pageNumber, sort, projection, referencedFields }) => {
  const [result, setResult] = useState([]);
  const [isLoading, setIsLoading] = useState(PROJECT_CONFIG.DATA_LOADING.DO_NOT_LOAD); 
  const [isRefreshing, setIsRefreshing] = useState(false);
  const [errorMessage, setErrorMessage] = useState('');
  const [noMoreResults, setNoMoreResults] = useState(false);
  const [totalGot, setTotalGot] = useState(0);
  const [requestData, setRequestData] = useState({
    collectionName, filter, pageSize, pageNumber: pageNumber || 0, sort, projection, referencedFields,
  });

  const find = async (loadExtraData) => {
    // if we call find function for an extra data and it's still refreshing - return
    // or if we call find for the first time return when 
    //    no more data
    //    already loading data (initial load | extra load)
    if ((loadExtraData && isRefreshing) || (!loadExtraData && (noMoreResults || isLoading !== PROJECT_CONFIG.DATA_LOADING.DO_NOT_LOAD))) 
      return;
    try {
      if (loadExtraData) 
        setIsRefreshing(true);
      else 
        setIsLoading(totalGot === 0 
          ? PROJECT_CONFIG.DATA_LOADING.FIRST_TIME_LOAD 
          : PROJECT_CONFIG.DATA_LOADING.EXTRA_ROUNDTRIP);

      const response = await db.getRecords({
          collectionName: requestData.collectionName,
          pageNumber: loadExtraData ? 0 : requestData.pageNumber,
          pageSize: requestData.pageSize || PROJECT_CONFIG.DEFAULT_PAGE_SIZE,
          sort: requestData.sort,
          filter: requestData.filter,
          projection: requestData.projection,
          referencedFields: requestData.referencedFields,
          language: 'en',
        }
      );

      if (response && response.result) {
        let newTotalGot = 0;

        // apply new result to existing data and set new total value
        if (loadExtraData) {
          setResult([...response.result]);

          newTotalGot = response.result.length;
          setTotalGot(newTotalGot);
        } 
        // set result for the first time and set total
        else {
          setResult([...result, ...response.result]);

          newTotalGot = totalGot + response.result.length;
          setTotalGot(newTotalGot);
        }

        // when all data is gathered set indicator noMoreResults as true. 
        if (newTotalGot >= response.totalCount) {
          setNoMoreResults(true);
        } 
        // otherwise allow extra roundtrips to the server
        else {
          setNoMoreResults(false);
        }
      }
      // setNextPage(nextPage + 1);
      setRequestData((prevData) => ({ ...prevData, pageNumber: loadExtraData ? 1 : prevData.pageNumber + 1 }));
    } catch (e) {      
      setErrorMessage('Something went wrong');
    } finally {
      // disable refreshing and loading indicators.
      if (loadExtraData) 
        setIsRefreshing(false);
      else 
        setIsLoading(PROJECT_CONFIG.DATA_LOADING.DO_NOT_LOAD);
    }
  };

  useEffect(() => {
    find();
  }, []);

  return [find, result, isLoading, errorMessage, noMoreResults, isRefreshing];
};

This custom hook waits for parameters like collectionName, filter, pageSize, pageNumber, sort, projection, referencedFields.

When something bad happens as a result indicator isLoading is set to false and the error message is saved to errorMessage property for later use. Otherwise, there is some logic that basically tries to save response to result property. On top of that, we return the total amount of records and if there is a need to call API again. This is very useful when you have a list in react native application and when you scrolling down you want to get more data. Knowing how much do you have total records in database and how much you fetched in your list, you can prevent extra roundtrips to your server.