Example of how to use fetch() with DataTables React component

Example of how to use fetch() with DataTables React component

aklietzaklietz Posts: 10Questions: 0Answers: 0

The DataTables React component is very nice.

I like how changing the data prop triggers a re-render. I utilized this behavior to implement a simple server-side fetch() that asynchronously populates the initial DataTable.

The following React component, called FetchDataTable, fetches the table data from a server using the browser API fetch. It is much easier to use than ajax for smaller tables.

FetchDataTable.jsx:

//
// FetchDataTable.jsx -- Wrapper for DataTables React component with fetch()
//

import { useState, useEffect, createContext } from 'react';

function useFetch(fetchUrl, fetchOptions) {
    const [tableData, setTableData] = useState([]/*empty table*/);
    const [isLoading, setIsLoading] = useState(true);
    const [errorMsg, setErrorMsg] = useState(null);

    useEffect(() => {

        const fetchData = async () => {

        try {
            setIsLoading(true);

            // fetch() default options
            const opts = Object.assign({
                method: 'GET', // or 'POST'
                cache: 'no-store',
                //credentials: 'same-origin', // default
                headers: { // if POST
                    'Content-Type': 'application/json; charset=utf-8',
                    //'Content-Type': 'application/x-www-form-url-encoded; charset=utf-8',
                },
                //body: JSON.stringify(data), // if POST
                redirect: 'error',
            }, fetchOptions);

            const response = await fetch(fetchUrl, opts);

            if (!response.ok) { // Got non-200 range response (404, etc)
                throw new Error(`Server request failed: Error ${response.status}`);
            }

            let text = await response.text();

            const json = JSON.parse(text); // Throws SyntaxError if bad JSON

            if (json.error) {
                throw new Error(json.error);
            }

            const data = json.data;

            if (Array.isArray(data)) {
                setTableData(data);
            } else {
                throw new Error('Server did not return data[]');
            }
        } catch(err) {
          setErrorMsg(err.message);
        } finally {
          setIsLoading(false);
        }
      };

      fetchData(); // Start the async data fetch

      return () => {
           // Do useEffect cleanup here
      };

    }, []/*once*/); // end useEffect()

    const props = {
        tableData,
        setTableData,
        errorMsg,
        isLoading
    };

    return props;
}

/*
Expected JSON response:
{
  data: [
        { "UserId": 123, "FirstName":"Bob", "LastName":"Smith", "Role": "Manager" },
        { "UserId": 456, "FirstName":"Roger", "LastName":"Kline", "Role": "Tech Support" },
        { "UserId": 789, "FirstName":"Julie", "LastName":"Adams", "Role": "Sales" }
  ]
}

If an error occurs, the expected JSON response is

{ error: "Error message" }
*/

////////////////////////////////////////////////////////////////////////////
//
// Wrapper for <DataTable data={tableData}>
//
//

// Context to pass the fetched data down to the DataTable component
export const FetchDataTableContext = createContext({});

export function FetchDataTable({fetchUrl, fetchOptions, children}) {

    // Note the use of braces {}, not []
    const {tableData, setTableData, errorMsg, isLoading} = useFetch(fetchUrl, fetchOptions);

    if (isLoading) {
        return (
            <h1>Loading data...</h1>
        );
    }

    if (errorMsg) {
        return (
            <p style={{ color: "red" }}>Error: {errorMsg}</p>
        );
    }

    const contextData = { tableData, setTableData };

    return(
        <FetchDataTableContext value={contextData}>
          {children}
        </FetchDataTableContext>
    );
}

The following is a simple React app that uses the FetchDataTable component to populate a DataTable.

The example uses Bootstrap 5.

App.jsx:

// Example App

import { createRoot } from 'react-dom/client';
import { useRef, useState, useEffect, useContext } from 'react';

import * as bootstrap from 'bootstrap';
import DataTable from 'datatables.net-react';
import DT from 'datatables.net-bs5';

import 'bootstrap/dist/css/bootstrap.css';

import { FetchDataTable, FetchDataTableContext } from './FetchDataTable';

DT.use(bootstrap);
DataTable.use(DT);

function MyDataTable()
{
    const dtTable = useRef(); // Create a DT ref (Normally only plain DOM elements are allowed here, but DataTable is a special case)

    //Provides context.tableData, context.setTableTable()
    const context = useContext(FetchDataTableContext);

    const options = {

        lengthMenu: [2, 10, 25, 50, 100],

        // Put other options here

        columns: [
            { data: 'UserId' },
            { data: 'FirstName' },
            { data: 'LastName' },
            { data: 'Role' },
        ],
    };

    return (
        <DataTable id="users" ref={dtTable}
              data={context.tableData}
              options={options}
              className="display table table-striped table-bordered">
            <thead>
                <th>UserId</th>
                <th>First Name</th>
                <th>Last Name</th>
                <th>Role</th>
            </thead>
            <tbody>
            </tbody>
        </DataTable>
    );
}

/////////////////////////////////////////////////////////////////////////////
//
function App() {

    const fetchUrl = 'https://my-site-name/my-api'
    const fetchOptions = { 'method': 'GET' };

    return (
      <div className="container">
        <FetchDataTable fetchUrl={fetchUrl} fetchOptions={fetchOptions}>
          <MyDataTable />
        </FetchDataTable>
      </div>
    );
}

/////////////////////////////////////////////////////////////////////////////

export const root = createRoot(document.getElementById("root"));

root.render(
    <App />
);

Replies

  • allanallan Posts: 65,328Questions: 1Answers: 10,836 Site admin

    Nice one - many thanks for sharing this with us!

    Allan

  • aklietzaklietz Posts: 10Questions: 0Answers: 0

    I forgot to mention that my example was written and tested with Vite.

    Vite allows the direct import of CSS, e.g., import 'bootstrap/dist/css/bootstrap.css'.

    Other bundlers might not support this. If the above example does not work, you may need to modify your project to import your CSS separately through an alternative method, such as including your own .css file inside your top-level index.html file, where your .css file has a CSS directive to @import the Bootstrap CSS file.

Sign In or Register to comment.