Example of how to use fetch() with DataTables React component
Example of how to use fetch() with DataTables React component
aklietz
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
Nice one - many thanks for sharing this with us!
Allan
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.htmlfile, where your .css file has a CSS directive to@importthe Bootstrap CSS file.