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 />
);