In this guide, I want to share my learnings and help you be productive in creating custom table components using React Table library (specifically V7). There are many utilties in the library and often it gets confusing to filter out the needs and how we can solve for them. In this post, we will focus on fetching data and rendering the table, connecting with a search box. For more details on V7, you can check the docs snapshot.
Step 1
Import the required libraries. I am assuming you have Apollo Client instance in the context of React Component tree being rendered - so we will use our hooks to query server.
import React, { useCallback, useMemo, useState, useEffect } from "react";
import { useLazyQuery } from "@apollo/react-hooks";
import { useTable, useGlobalFilter } from "react-table";
Step 2
I am explaining with LazyQuery, which could be very handy when your data fetching is dependent on other remote data.
const [getData, { loading, error, data }] = useLazyQuery(QUERY_DATA, {
onCompleted: () => {
// log success
},
onError: (err) => {
// log error
},
});
it’s a good pattern to use onCompleted, onError callbacks to ensure proper logging of messages - as shown above. Developers often tend to use useEffect but this pattern could cause unwanted callback triggering and repeated message logging skewing the overall logs - I would request you to avoid using useEffect.
Step 3
Once you get the data, probably you would want to format it. How would you go about it in React to ensure the re-renders are controlled? We use useMemo.
// data fetched using Lazy Query.
const dataFromAPI = [
{
firstname: "Tony",
lastname: "Stark",
},
{
firstname: "Robert",
lastname: "Downey",
},
];
// Formatted data
const data = useMemo(() => {
if (!dataFromAPI) return [];
return formatData(dataFromAPI);
}, [dataFromAPI]);
Step 4
Lets render the formatted data. For this we would need to wire up how the columns are related to the data we are passing to react-table hooks. We need to add accessor which can map to the data attributes, and we could also provide a custom render logic for each cell data. To render this, we can have the table config as below. React table expect memoized columns in case there is a change dependent on props etc.
cosnt columns = useMemo(() => {
return [{
Header: 'First Name',
accessor: 'firstname', // mapping to the data attribute `firstname`
},
{
Header: 'Last Name',
accessor: 'lastname', // mapping to the data attribute `lastname`
}]
});
Step 5
Lets wire up the hooks provided by react-table library.
const {
headerGroups,
prepareRow,
rows,
state: { globalFilter },
setGlobalFilter,
} = useTable(
{
columns, // from the previous snippet containing mapping to data attributes
data,
},
useGlobalFilter
);
Step 6
Now the data is connected to the internals of the library, and we have headerGroups containing header details, and
return (
<table>
<thead>
{headerGroups.map((headerGroup) => (
<tr>
{headerGroup.headers.map((column) => (
<th>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map((row) => {
prepareRow(row);
return (
<tr>
{row.cells.map((cell) => (
<td>{cell.render("Cell")}</td>
))}
</tr>
);
})}
</tbody>
</table>
);
Adding Search
How can we connect an external Search box component to this table? As you would have seen there is useGlobalFilter hook we added into the react-table. This gives access to globalFilter and setGlobalFilter. This is like an overall search across all data points in the table. We can trigger this from our external component to influence the data displayed in the table.
Here is a sample search box component wired up with Global Filter.
const SearchBox = ({ setGlobalFilter, globalFilter }) => {
const handleChange = (evt) => {
setGlobalFilter(evt.target.value || undefined);
};
return <input value={globalFilter || ""} onChange={handleChange} />;
};
That’s all. Now you have a table which is rendering data from remote APIs, and then you have a search box integrated for making a text search across the table data.
Follow for more!