javascriptreactjsreact-data-table-component

Request to api works fine in component but not when using provider with react


When making a request to my API from a component and using react-data-table-component everything works perfectly but if I try to make the request from my Product Provider the pagination is incorrect and no longer works as expected.

With this code I make the request, datatable and pagination from my component working perfectly:

import React, { useState, useEffect, useCallback, useMemo } from "react";
import axiosClient from "../config/axiosClient";
import DataTable from 'react-data-table-component-with-filter'
import { CSVLink } from "react-csv"
import { Link } from 'react-router-dom'
import useProducts from "../hooks/useProducts";

const removeItem = (array, item) => {
  const newArray = array.slice();
  newArray.splice(newArray.findIndex(a => a === item), 1);

  return newArray;
};

const ProductsTest = () => {
  

  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(false);
  const [totalRows, setTotalRows] = useState(0);
  const [perPage, setPerPage] = useState(10);
  const [currentPage, setCurrentPage] = useState(1);
  const [searchBox, setSearchBox] = useState('')

  
  const STRING_TRADUCTIONS = { "KILOGRAM" : "KILOGRAMOS", "GRAMS" : "GRAMOS", "BOX" : "CAJA", "PACKAGE" : "PAQUETE", "BOTTLE" : "BOTE", "PIECES" : "PIEZAS", "BAG" : "BOLSA", "LITER" : "LITRO" }

  const fetchUsers = async (page, limit = perPage, search = searchBox) => {
    setLoading(true)
    const dataOnLs = localStorage.getItem('cmtjs')

    const config = {
      headers: {
          "Content-Type": "application/json",
          apiKey: dataOnLs
      } ,
      params: {
          limit,
          page, 
          search
        }            
  }
    const response = await axiosClient(`/products`, config)
    const data = response.data.products.docs.map( doc => (
      {
          _id: doc._id,
          idProduct: doc.idProduct,
          barCode: doc.barCode,
          name: doc.name,
          presentation: STRING_TRADUCTIONS[doc.presentation],
          salePrice: doc.salePrice,
          purchasePrice: doc.purchasePrice,
          stock: doc.stock,
          user: doc.user.username,
          category: doc.category.name,
          provider: doc.provider.name
      }
  ))
      setData(data);
      setTotalRows(response.data.products.totalDocs);
      setLoading(false);
  };

  useEffect(() => {
    fetchUsers(1)
  }, []);

  const columns = useMemo(
    () => [
      {
        name: "ID",
        selector: "idProduct",
        sortable: true
      },
      {
        name: "Código de Barras",
        selector: "barCode",
        sortable: true
      },
      {
        name: "Nombre",
        selector: "name",
        sortable: true
      },
      {
        name: "Presentación",
        selector: "presentation",
        sortable: true
      },
      {
        name: "Precio",
        selector: "salePrice",
        sortable: true
      },
      {
        name: "Stock",
        selector: "stock",
        sortable: true
      },
     { cell: row => 
      <Link to={ `/dashboard/product/${row._id}`}>
        <button className='btn btn-ghost text-xs'>
            Mas
        </button>
      </Link>}
    ]
  );

  const handlePageChange = page => {
    fetchUsers(page);
    setCurrentPage(page);
  };

  const handlePerRowsChange = async (newPerPage, page) => {
    fetchUsers(page, newPerPage);
    setPerPage(newPerPage);
  }

  const headers = [
    { label: "ID", key: "idProduct" },
    { label: "Código de Barras", key: "barCode" },
    { label: "Nombre", key: "name" },
    { label: "Presentación", key: "presentation" },
    { label: "Precio Venta", key: "salePrice" },
    { label: "Precio Compra", key: "purchasePrice" },
    { label: "Stock", key: "stock" },
    { label: "Creador", key: "user" },
    { label: "Categoría", key: "category" },
    { label: "Proveedor", key: "provider" }
  ]

  const paginationComponentOptions = {
    rowsPerPageText: 'Mostrar',
    rangeSeparatorText: 'de',
    selectAllRowsItem: true,
    selectAllRowsItemText: 'Todos',
};

const clear = () => { 
  setPerPage(10)
  setSearchBox('')
  fetchUsers(1, 10, '')
}

  return (

    <div>
      <input type="text" onChange={(e)=> setSearchBox(e.target.value)}/>
      <button onClick={ ()=> fetchUsers()}>Buscar</button>

      <CSVLink data={data} headers={headers} filename={"productos.cdtmx.csv"} className="cursor-pointer">
        <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAABmJLR0QA/wD/AP+gvaeTAAACnUlEQVRoge2YXUhUQRiGn7HVRVddc8k0QawQw6hQohKSlsp+oDulQBGCbuxGQ1itDIx+IIWINKK86S4o6ibFEG9ywQzCEJLVSCylorYf1CJaXXe6SPvZ1ePZs3tczfNcfsP3zfvOzHc4M2BgYGAQSYTWxJSrReUg6oHEYHNtuRu1TDkupKx27Tp38+9glJZKv9AmPgQSpYiq9w+GYGBBxU8jrf6RUAwsCgwDkcYwEGlM/gFLVf4JkBcEWJQSvw+/Ua4cJYixWjElxoemcB4CDKgRrwqfZGJ0lImpqYChFe53IZefIeAIhUX8DDJsleZkyffA8jBgNkXTe+ouXy93U2Ev+R3PSE7jU70T96VHZNrSdROphCoDHu8kZ1quAVC1u4x4cxwAdQfLMZuiaei4xevPb/VTqYDqI9Ta56Rj4Ak2SxLHCw6zaU0Wxbl7Gfw4QlPnbT01KjLLZ3RuTj9oxJ61lQp7CYUbdiAQVN5rwOOd1EvfvATVxAMfXtHcdZ+k2ATy127hzrN2nIM9emlTRVA7AJAwff4BhJj/QmfpDuwNT0pCsNPOSVA7ULA+j7Jth3g63EfXUC9H8vazJ3t72MRoQbUBsymaK0UOhBCcbbtBbUsTUkoai2uIi4nVU6Miqg2c3HeM7NWZtPc/xjnYQ8+Ii9Y+JxnJadQUHtVToyKqDOSkrqPSXopP+jj/8M+jQF3bdby+KSrspWxOz9JNpBKqmtj1fojk6oKA+Ev3CCsdO8MuKhiWx7/QYmY2A9/CVt2n/4Vgth6oBS4Cod0FfVKa3Z4fgMd/yPb8i+ay/pma30ZTD+RoXl5rxiqtqbxo7vxH83/ZA0sKw0CkWdYGxsOmQj1j/gHNBoQQDhABBXVkTEocCzifgYGBgQp+AlpMnf09Cu/RAAAAAElFTkSuQmCC"/>
      </CSVLink>      
      <DataTable
        columns={columns}
        data={data}
        progressPending={loading}
        pagination
        paginationServer
        paginationTotalRows={totalRows}
        paginationDefaultPage={currentPage}
        onChangeRowsPerPage={handlePerRowsChange}
        onChangePage={handlePageChange}
        selectableRows
        //onSelectedRowsChange={({ selectedRows }) => console.log(selectedRows)}
        paginationComponentOptions={paginationComponentOptions}
        noDataComponent="No hay resultados"
      />
      {
        searchBox && searchBox !== ''  &&  <button onClick={ () => clear() }>Limpiar</button>
      }
      
    </div>
  )
}

But I have my product provider where I make a get request to all my products, avoiding making the requests from my component and having the data globally, but if I use "getProducts" from my provider, the first view of the datatable is correct however , when clicking on a page or next, the pager advances but the data displayed does not, for example: it shows me the first 10 records but I ask for the next 10 and the pager advances correctly but the data is still the first 10 records, No I know how to use my provider and make the pager show the following data depending on what the user needs.

This is the code of my provider to obtain the product data


    const getProducts = async(page, limit, search) => { 
       
        const dataOnLs = localStorage.getItem('cmtjs')

        const config = {
            headers: {
                "Content-Type": "application/json",
                apiKey: dataOnLs
            } ,
            params: {
                page,
                limit,
                search
              }            
        }
        try {
            const { data } = await axiosClient(`/products`, config)
            setProducts(data)
        } catch (error) {
            
        

So in my component I call "getProducts" from my provider to have the products data in "products" using the useEffect hook

  useEffect(() => {
    getProducts()
    setData(products.products?.docs)
    setTotalRows(products.products?.totalDocs)
    setLoading(false)
  }, []);

In my paginator to obtain the following 10 product records, I click and the text changes that indicates which page is being shown, but the data remains the same as the first page

  const handlePageChange = page => {
    getProducts(page); // it does not show the next 10 records as it happened in the fetch of my component
    setCurrentPage(page); // OK
  };

pagination error

In the same way, my browser no longer works using it in this way, I only changed the function to call my provider now, but it does not work

      <input type="text" onChange={(e)=> setSearchBox(e.target.value)}/>
      <button onClick={ ()=> getProducts()}>Buscar</button>

I would like to know if you can help me to make my datatable and browser work using my provider. Thanks.


Solution

  • In this code

     useEffect(() => {
        getProducts()
        setData(products.products?.docs)
        setTotalRows(products.products?.totalDocs)
        setLoading(false)
      }, []);
    

    You are calling getProducts(), which is asynchronous. Then you try setData(products...), but the asynchronous call did not finish yet, so products was not updated yet. When eventually the asynchronous code terminates, the useEffect statement is not triggered again, because the dependency array states that the effect is only executed when the component mounts.

    Split up your effect in two parts instead, so the second effect gets triggered when new products are available:

     useEffect(() => {
        getProducts()
      }, []);
    
     useEffect(() => {
        setData(products.products?.docs)
        setTotalRows(products.products?.totalDocs)
        setLoading(false)
      }, [products]);