Tables are everywhere. Even if the web (thankfully) moved away from using tables to layout sites, you will still find tables in many places. Many lists are built from tables. User grids, preferences, favorites, feeds, etc. Because tables are everywhere, you, as a React developer, will find yourself doing one of two things: Using a library to build tables or manually building your own. If you fall into the latter category, this post is for you.
We're going to create a custom, reusable table component in React. In order to do so, we need to establish some high-level goals. These are the goals I applied to tables in my current project, Perligo, and I think they apply across all projects.
- The table must be usable multiple times in different contexts (this one seems obvious, but putting it here to be sure).
- The table should take in two data properties: the table head labels and the table body data.
- The table should allow for custom classes to be applied depending on where the table is used.
With that in mind, let's get started. I'm not going to go through the whole set-your-react-project-up process. But, you'll obviously need a React project to continue.
Our table component will actually be a container that holds two other components: TableHeadItem and TableRow.
The Table Component
The table component is most important because it is what you will import into the pages you plan to use your table. Let's create it now.
import React from "react";
import TableRow from "./TableRow";
import TableHeadItem from "./TableHead";
const Table = ({ theadData, tbodyData, customClass }) => {
return (
<table className={customClass}>
<thead>
<tr>
{theadData.map((h) => {
return <TableHeadItem key={h} item={h} />;
})}
</tr>
</thead>
<tbody>
{tbodyData.map((item) => {
return <TableRow key={item.id} data={item.items} />;
})}
</tbody>
</table>
);
};
export default Table;
You can see we are importing the TableHeadItem and the TableRow components. We haven't built those yet, but we will shortly.
The Table component takes in three props: theadData
, tbodyData
, and customClass
. The customClass
prop satisfies the last requirement from my list of requirements for our reusable table component. If one is passed through, the table element will have that class. Otherwise, the class will be undefined.
The other two props, theadData
, and tbodyData
are both arrays. Remember, we want our table component to be reusable and not to care about the data being passed in. For that to work, we need to standardize the data props. Arrays work best for tables. As you'll see soon, the theadData
should be an array of strings, and the tbodyData
should be an array of arrays.
You'll see how this works soon. Let's move on to the TableHeadItem component.
The TableHeadItem Component
import React from "react";
const TableHeadItem = ({ item }) => {
return (
<td title={item}>
{item}
</td>
);
};
export default TableHeadItem;
Not much to it. If you remember, we're mapping our theadData
array inside the Table component, so the result passed through to the TableHeadItem component is a string that we just need to render.
Finally, let's build the TableRow component.
The TableRow Component
import React from "react";
const TableRow = ({ data }) => {
return (
<tr>
{data.map((item) => {
return <td key={item}>{item}</td>;
})}
</tr>
);
};
export default TableRow;
Again, this is a pretty simple component. If you remember, tbodyData
is an array of arrays. In our Table component, we're mapping over the root level array and returning a single array to each TableRow component that gets rendered. Within the TableRow component, we map over that individual array and return the columns for the row.
Putting It All Together
We've now built everything we need. Let's connect the component and pass it some data. You can do this from any component where you'd like to render tabular data. Let's start with the shape of the data:
const theadData = ["Name", "Email", "Date"];
const tbodyData = [
{
id: "1",
items: ["John", "john@email.com", "01/01/2021"]
},
{
id: "2",
items: ["Sally", "sally@email.com", "12/24/2020"]
},
{
id: "3",
items: ["Maria", "maria@email.com", "12/01/2020"]
},
]
You'll note that my tbodyData
variable is not actually an array of arrays. It's an array of objects, each with an array nested in the object. This is simply because I prefer to use a unique identifier for my key prop in React when mapping over data. You don't have to do this. You could just as easily, strip out the object and return just an array of arrays as I suggested originally.
Inside the component where we'd like to render the table, you can do something like this:
import React from "react";
import Table from "../../Reusables/Table";
const Example = () => {
const theadData = ["Name", "Email", "Date"];
const tbodyData = [
{
id: "1",
items: ["John", "john@email.com", "01/01/2021"],
},
{
id: "2",
items: ["Sally", "sally@email.com", "12/24/2020"],
},
{
id: "3",
items: ["Maria", "maria@email.com", "12/01/2020"],
},
];
return (
<div>
<Table theadData={theadData} tbodyData={tbodyData} />
</div>
);
};
export default Example;
You can optionally pass in a customClass
prop to your table component as well. This table component is now very reusable, quick to drop in, and prepping the data that you send through as props is easy.
I am using this custom component in my app (not yet released, but pre-release sign-ups are available), Perligo. You can see how nicely the tables render with data passed in exactly as I structured it above.
Conclusion
There are plenty of libraries and frameworks that are dedicated to table components or offer a full array of components. However, if you want a lightweight, customizable solution, building things yourself is always a valid approach. I hope this quick tutorial was helpful.