3.0.4 • Published 5 years ago

react-data-table-component-tmp v3.0.4

Weekly downloads
2
License
MIT
Repository
github
Last release
5 years ago

Build Status npm version codecov Storybook

React Data Table Component

Creating yet another React table library came out of necessity while developing a web application for a growing startup. I discovered that while there are some great table libraries already available, most required heavy customization or lacked basic features such as built in sorting and pagination, and in some cases required a restrictive license.

If you want to achieve balance with the force and want a simple but flexible table library give React Data Table Component a chance. If you require an Excel clone and need to pivot large data sets then this is not the React table library you are looking for 👋

Table of Contents

Demo and Examples

React Data Table Component Demo

Key Features

  • Declarative Configuration
  • Sortable (client)
  • Selectable Rows
  • Expandable Rows
  • Themeable via js config
  • Data Aware (i.e. easily callback to a parent component get the DataTable state, e.g. selectedRows
  • Responsive (via x-scroll/flex)
  • Pagination

Requirements

React Data Table Component requires the following be installed in your project:

  • React 16.8.0+
  • styled-components 3.2.3+ || 4.0.0+ || 5.0.0+

Installation

React Data Table requires the wonderful styled-components library. If you've already installed styled-components there is no need to install it again.

npm install react-data-table-component styled-components

or

yarn add react-data-table-component styled-components

Logging Issues and Contributions

Please use the github issue templates feature for logging issues or feature proposals. Including a codesanbox and providing clear details on the feature/issue will elicit a much quicker response 😉

API and Usage

Columns

Nothing new here - we are using an array of object literals and properties to describle the columns:

PropertyTypeRequiredExample
namestringnothe display name of our Column e.g. 'Name'
selectorstringyesthe propery in the data set e.g. property1.nested1.nested2.
sortableboolnoif the column is sortable
formatfuncnoformat the selector e.g. row => moment(row.timestamp).format('lll')
cellfuncnofor ultimate control use cell to render your own custom component! e.g row => <h2>{row.title}</h2> Negates format
grownumbernoflex-grow of the column. the is useful if you want a column to take up more width than its relatives (without having to set widths explicitly). this will be affected by other columns where you have explicitly set widths
widthstringnogive the column a fixed width
minWidthstringnogive the column a minWidth
maxWidthstringnogive the column a maxWidth
rightboolnoright aligns the content in the cell. useful for numbers
centerboolnocenter aligns the content in the cell
compactboolnoreduces the padding in the cell by 50%
buttonboolnoapplies additional styling when using a button
wrapboolnowhether the cell content should be allowed to wrap.
allowOverflowboolnoallows content in the cell to overflow. useful for menus/layovers that do not rely on "smart" positioning
ignoreRowClickboolnoprevents the onRowClicked event from being passed on a specific TableCell column. This is really useful for a menu or button where you do not want the onRowClicked triggered
hideinteger or string preset (sm, md, lg)nospecify a screen size (breakpoint) as an integer (in pixels) that hides the column when resizing the browser window. You can also use the preset values of: sm (small), md(medium), and lg(large)

column.hide media presets

When the breakpoint is reached the column will be hidden. These are the built-in media breakpoint presets when hiding columns

ValueBreakpointDescription
sm599pxsmall (phones)
md959pxmedium(landscape tablets)
lg1280pxlarge(laptops/desktops)

DataTable Properties

Basic

PropertyTypeRequiredDefaultDescription
titlestring or componentnoThe Title displayed in the Table Header
columnsarrayyes[]The column configuration
dataarrayno[]it is highly recommended that your data has a unique identifier (keyField). The default keyField is id. If you need to override this value then see keyField DataTable Properties.
keyFieldstringno'id'your data should have a unique identifier. By default, React Data Table looks for an id property for each item in your data. You must match keyField to your identifier key, especially if you want to manage row state at a later time or use the expander feature. If a unique id is not present, React Data Table will use the row index (not recommended) as the key value
stripedboolnofalsestripe color the odd rows
highlightOnHoverboolnofalseif rows are to be highlighted on hover
pointerOnHoverboolnofalseif rows show a point icon on hover
noDataComponentstring or componentnoA custom component to display when there are no records to display
classNamestringnooverride the className on the Table wrapper
styleobjectnooverride the style on the Table wrapper
responsiveboolnotruemakes the table horizontally scrollable on smaller screen widths
customThemeobjectnoOverride the default theme, by overriding specifc props. Your changes will be merged. See Theming for more information
disabledboolnofalsedisables the Table section
onRowClickedfuncnocallback to access the row data,index on row click
overflowYboolnofalseif a table is responsive, items such as layovers/menus/dropdowns will be clipped on the last row(s) due to to overflow-x-y behavior - setting this value ensures there is invisible space below the table to prevent "clipping". However, if possible, the correct approach is to use menus/layovers/dropdowns that support smart positioning. If used, the table parent element must have a fixed height or height: 100%.
overflowYOffsetstringno250pxused with overflowY to "fine tune" the offset

Progress Indicator

PropertyTypeRequiredDefaultDescription
progressPendingboolnodisables the table and displays a plain text Loading Indicator
progressComponentcomponentnoallows you to use your own custom progress component
progressCenteredboolnoabsolutely position and center the progress over the table

Row Selection

PropertyTypeRequiredDefaultDescription
selectableRowsboolnofalseWhether to show selectable checkboxes
clearSelectedRowsboolnofalsetoggling this property clears the selectedRows. If you use redux or react state you need to make sure that you pass a toggled value or the component will not update. See Clearing Selected Rows
onRowSelectedfuncnocallback to access the row selected state ({ allSelected, selectedCount, selectedRows }). It's highly recommended that you memoize any handlers that you pass to onRowSelected to prevent DataTable from unnescessary re-renders
selectableRowsComponentfuncnoOverride the default checkbox component - must be passed as a function (e.g. Checkbox not <Checkbox />)
selectableRowsComponentPropsobjectnoAdditional props you want to pass to selectableRowsComponent. See Advanced Selectable Component Options to learn how you can override indeterminate state

Row Expander

PropertyTypeRequiredDefaultDescription
expandableRowsboolnofalseWhether to make a row expandable, if true it requires an expandableRowsComponent. It is highly recommended your data set have a unique identifier defined as the keyField for row expansion to work properly.
expandableIconobjectnodefault expander iconsyou may pass in your own custom icons using the expandableIcon: { collapsed: <svg>...</svg>, expanded: <svg>...</svg>
expandableDisabledFieldstringnoReact Data Table looks for this property in each item from your data and checks if that item can be expanded or not. You must set a bool value in the expandableDisabledField of your data if you want to use this feature.
defaultExpandedFieldstringnoReact Data Table looks for this property in each item from your data and checks if that item should be expanded on initial render. You must set a bool value in the defaultExpandedField field of your data if you want to use this feature.
expandableRowsComponentstring or componentnoA custom component to display in the expanded row. It will have the data prop composed so that you may access the row data

Sorting

PropertyTypeRequiredDefaultDescription
defaultSortFieldstringnoSetting this ensures the table data is presorted before it renders and the field(selector) is focused
defaultSortAscboolnotrueset this to false if you want the table data to be sorted in DESC order
sortIconcomponentnoOverride the default sort icon - the icon must be a font or svg icon and it should be a "downward" icon since animation will be handled by React Data Table
onSortfuncnocallback to access the sort state when a column is clicked. returns (column, sortDirection, event)
sortFunctionfuncnopass in your own custom sort function e.g. `(rows, field, direction) => {...yourSortLogicHere}. you must return an array

Pagination

PropertyTypeRequiredDefaultDescription
paginationboolnofalseenable pagination with defaults. by default the total record set will be sliced depending on the page, rows per page. if you wish to use server side pagination then use the paginationServer property
paginationServerboolnofalsechanges the default pagination to work with server side pagination
paginationDefaultPagenumberno1the default page to use when the table initially loads
paginationTotalRowsnumberno0allows you to provide the total row count for your table as represented by your API when performing server side pagination. if this property is not provided then react-data-table will use data.length
paginationPerPagenumberno10the default rows per page to use when the table initially loads
paginationRowsPerPageOptionsnumberno10, 15, 20, 25, 30row page dropdown selection options
onChangePagefuncnonullcallback when paged that returns onChangePage(page, totalRows)
onChangeRowsPerPagefuncnonullcallback when rows per page is changed returns onChangeRowsPerPage(currentRowsPerPage, currentPage)
paginationComponentfuncnoPaginationa component that overrides the default paginator component
paginationComponentOptionsobjectnoSee Descriptionoverridable options for the built in pagination component. If you are developing a custom pagination component you can use paginationComponentOptions to pass in your own custom props. Defaults to: { rowsPerPageText: 'Rows per page:', rangeSeparatorText: 'of' }
paginationIconFirstPagenoJSXa component that overrides the first page icon for the pagination
paginationIconLastPagenoJSXa component that overrides the last page icon for the pagination
paginationIconNextnoJSXa component that overrides the next page icon for the pagination
paginationIconPreviousnoJSXa component that overrides the previous page icon for the pagination

Header

PropertyTypeRequiredDefaultDescription
actionscomponent or array of componentsnoadd actions to the TableHeader
contextTitlestringnooverride the context menu title
contextActionscomponent or array of componentsnoadd context actions to the TableHeader context
noHeaderboolnofalseremoves the table header. title, contextTitle and contextActions will be ignored
fixedHeaderboolnofalsemakes the table header fixed allowing you to scroll the table body
fixedHeaderScrollHeightstringno100vhin order for fixedHeader to work this property allows you to set a static height to the TabelBody. height must be a fixed value
subHeadercomponent or array of componentsnofalseshow a subheader between the table and table header
subHeaderAlignstringnorightalign the subheader content (left, right, center)
subHeaderWrapboolnotruewhether the subheader content should wrap
subHeaderComponentcomponent or array of componentsno[]a component you want to render

Advanced Selectable Component Options

Sometimes 3rd party checkbox components have their own way of handling indeterminate state. We don't want React Data Table hardcoded to a specific ui lib or custom component, so instead a "hook" is provided to allow you to pass a function that will be resolved by React Data Table's internal Checkbox for use with indeterminate functionality.

Example Usage:

const { Checkbox } from 'react-md';

...

/*
  In this example, the react-md ui lib determines its own indeterminate state via the `uncheckedIcon` property.
  Let's override it. React Data Table is made aware if a checkbox is indetermite or not becuase internally we can resolve this   as `yourfunction(checkboxawareindeterminatestate)`
*/

const handleIndeterminate = isIndeterminate => (isIndeterminate ? <FontIcon>indeterminate_check_box</FontIcon> : <FontIcon>check_box_outline_blank</FontIcon>);

const MyComponent = () => (
  <DataTable
    title="Arnold Movies"
    columns={columns}
    data={data}
    selectableRows
    selectableRowsComponent={Checkbox} // Pass the function only
    selectableRowsComponentProps={{ uncheckedIcon: handleIndeterminate  }}
  />
);

Note This is currently only supported for indeterminate state, but may be expanded in the future if there is a demand

Basic Table

The following declarative structure creates a sortable table of Arnold movie titles:

import DataTable from 'react-data-table-component';

const data = [{ id: 1, title: 'Conan the Barbarian', year: '1982' } ...];
const columns = [
  {
    name: 'Title',
    selector: 'title',
    sortable: true,
  },
  {
    name: 'Year',
    selector: 'year',
    sortable: true,
    right: true,
  },
];

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        data={data}
      />
    )
  }
);

Selectable Rows

Let's make our rows selectable so we can access the selected results

...

const handleChange = (state) => {
  // You can use setState or dispatch with something like Redux so we can use the retrieved data
  console.log('Selected Rows: ', state.selectedRows);
};

class MyComponent extends Component {
  render() {
      return (
        <DataTable
          title="Arnold Movies"
          columns={columns}
          data={data}
          selectableRows // add for checkbox selection
          onRowSelected={handleChange}
        />
    )
  }
);

Clearing Selected Rows

We need some hook to trigger all the selectedRows to clear. If you were building your own table component, you would manage the selected rows state in some parent component, however, in our case, since we to keep row management within React Data Table, a clearSelectedRows prop is provided so you can pass a toggled state.

It will be up to you to make sure you do not pass the same state twice. For example, if you set clearSelectedRows={true} twice, on the second update/trigger, none the rows will not be cleared.

...
// set the initial state
state = { toggledClearRows: false }
...

const handleChange = (state) => {
  // You can use setState or dispatch with something like Redux so we can use the retrieved data
  console.log('Selected Rows: ', state.selectedRows);
};

// Toggle the state so React Table Table changes to `clearSelectedRows` are triggered
const handleClearRows = () => {
  this.setState({ toggledClearRows: !this.state.toggledClearRows})
}

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        data={data}
        selectableRows // add for checkbox selection
        onRowSelected={handleChange}
        clearSelectedRows={this.state.toggledClearRows}
      />
    )
  }
);

Overriding with a 3rd Party Ui Component Library

Don't like those ugly html checkboxes? Let's override them with some react-md sexyiness. While we are at it we will also override the sortIcon:

...
import { Checkbox, FontIcon } from 'react-md';
...

class MyComponent extends Component {
  render() {
    return (
      title="Arnold Movies"
      columns={columns}
      data={data}
      selectableRows
      selectableRowsComponent={Checkbox} // Pass the function only
      selectableRowsComponentProps={{ inkDisabled: true }} // optionally, pass react-md supported props down to our custom checkbox
      sortIcon={<FontIcon>arrow_downward</FontIcon>} // use a material icon for our sort icon. rdt will rotate the icon 180 degrees
      onRowSelected={handleChange}
    />
    )
  }
);

Custom Cells

Let's give our Movie list a summary, but in the same cell as Name:

....

const data = [{ id: 1, title: 'Conan the Barbarian', summary: 'Orphaned boy Conan is enslaved after his village is destroyed...',  year: '1982' } ...];
const columns = [
  {
    name: 'Title',
    sortable: true,
    cell: row => <div><div style={{ fontWeight: bold }}>{row.title}</div>{row.summary}</div>,
  },
  {
    name: 'Year',
    selector: 'year',
    sortable: true,
    right: true,
  },
];

...

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        data={data}
        selectableRows
        selectableRowsComponent={Checkbox}
        selectableRowsComponentProps={{ inkDisabled: true }}
        sortIcon={<FontIcon>arrow_downward</FontIcon>}
        onRowSelected={handleChange}
      />
    )
  }
);

Expandable Rows

Let's make our rows expandable so we can view more details:

...

const data = [{ id: 1, title: 'Conan the Barbarian', summary: 'Orphaned boy Conan is enslaved after his village is destroyed...',  year: '1982', image: 'http://conan.image.png' } ...];
const columns = [
  {
    name: 'Title',
    sortable: true,
    cell: row => <div><div style={{ fontWeight: 700 }}>{row.title}</div>{row.summary}</div>,
  },
  {
    name: 'Year',
    selector: 'year',
    sortable: true,
    right: true,
  },
];

...

// The row data is composed into your custom expandable component via the data prop
const ExpanableComponent = ({ data }) => <img src={data.image} />;

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        data={data}
        selectableRows
        selectableRowsComponent={Checkbox}
        selectableRowsComponentProps={{ inkDisabled: true }}
        sortIcon={<FontIcon>arrow_downward</FontIcon>}
        onRowSelected={handleChange}
        expandableRows
        expandableRowsComponent={<ExpanableComponent />}
      />
    )
  }
);

But in some cases we don't have more details to show:

...

const data = [{ id: 1, title: 'Conan the Barbarian', summary: 'Orphaned boy Conan is enslaved after his village is destroyed...',  year: '1982', expanderDisabled: true, image: 'http://conan.image.png' } ...];
const columns = [
  {
    name: 'Title',
    sortable: true,
    cell: row => <div><div style={{ fontWeight: 700 }}>{row.title}</div>{row.summary}</div>,
  },
  {
    name: 'Year',
    selector: 'year',
    sortable: true,
    right: true,
  },
];

...

// The row data is composed into your custom expandable component via the data prop
const ExpanableComponent = ({ data }) => <img src={data.image} />;

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        data={data}
        selectableRows
        selectableRowsComponent={Checkbox}
        selectableRowsComponentProps={{ inkDisabled: true }}
        sortIcon={<FontIcon>arrow_downward</FontIcon>}
        onRowSelected={handleChange}
        expandableRows
        expandableDisabledField="expanderDisabled"
        expandableRowsComponent={<ExpanableComponent />}
      />
    )
  }
);

Optimizing for Performance and Caveats

Pre-optimizaton can be the root of all evil, however, there are some best practices you can adhere to that will ensure React Data Table (RDT) is giving you the performance that you expect.

Passing non-primitive props (objects, arrays and functions)

While RDT has internal optimizations to try and prevent re-renders on deeper internal components, for peak performance it's up to you to make sure that you understand how React manages rendering when props/state change as well as how JavaScript determines equality for non-primitives. As a general rule, or if you are experiencing performance issues you should ensure that any non-primitive property that's passed into RDT is not re-created on every render cycyle. This is even more important when you have larger data sets or you are passing complex components and columns to DataTable.

Optimizing Class Components

You can typically achieve this by moving props such as objects, arrays, functions or other React compents that you pass to RDT outside of the render method. Additionally, RDT provides you with a memoize helper for cases where you are using a function to generate those values.

Examples of Optimizations

The following component will cause RDT to fully re-render everytime onRowSelected is triggered. Why? Becuase when setState is called it triggers myComponent to re-render which by design triggers a re-render on all child components i.e. DataTable. But luckily for you React optimally handles this descision on when and how to re-render DataTable and a full re-render should not occur as long as DataTable props are the same.

However, in the example below columns changes on every re-render becuase it's being re-created. This is due to referential equality checking, simply: columns[] !== columns[]. In other words, while both instances of columns contain the same elements, they are "different" arrays.

Bad

...
import React, { Component } from 'react';
import DataTable from 'react-data-table';

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows }); // triggers MyComponet to re-render with new state
  }

  render () { // by design runs on every setState trigger
    // upon re-render columns array is recreated and thus causes DataTable to re-render
    const columns = [....];

    return (
      <DataTable
        data={data}
        columns={columns}
        onRowSelected={this.updateState}
        selectableRows
      />
    )
  }
}

A "solution" could be to declare any field that is a non primitive field outside of the render function so that it does not get recreated on every re-render cycle:

Good

...
import React, { Component } from 'react';
import DataTable from 'react-data-table';

const columns = [....]; // is only created once

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows });
  }

  render () {

    return (
      <DataTable
        data={data}
        columns={columns}
        onRowSelected={this.updateState}
        selectableRows
      />
    )
  }
}

But that only works if you don't need to pass component props/methods to the column object. For example what if you want to attach an event handler to a button in the row using column.cell?

const columns = [;
  {
    cell: () => <Button raised primary onClick={this.handleAction}>Action</Button>,
    ignoreRowClick: true,
    allowOverflow: true,
    button: true,
  },
  ...
];

So how do we attach event handlers to our columns without having to place it in the render method and dealing with unnescessary re-renders?

  1. Create a columns function and pass the arguments needed
  2. Memoize the columns function

This way, when React checks if columns has changes columns will instead be cached result (remember referential equality), thus no unnessesary re-render.

Got it? Let's try this again with the optimal solution:

import React, { Component } from 'react';
import DataTable, { memoize } from 'react-data-table';

const onRowSelected = memoize(handleAction => [
  ...
  {
    cell: () => <Button raised primary onClick={handleAction}>Action</Button>,
    ignoreRowClick: true,
    allowOverflow: true,
    button: true,
  },
  ...
]);

class MyComponent extends Component {
  updateState = state => {
    this.setState({ selectedRows: state.selectedRows });
  }

  render () {
    return (
      <DataTable
        data={data}
        columns={columns(this.updateState)}
        onRowSelected={this.updateState}
        selectableRows
      />
    );
  }
}

Notice that this.updateState does not require memoization. That's because this.updateState is defined as a class method and therefore only created once. This however, is a different matter with function components.

Optimizing Functional Components

If you're building functional components you get access to React Hooks such as useMemo and useCallback. In this example, simply wrap columns in a useMemo callback and your updateState into useCallback:

import React, { useState, useMemo } from 'react';
import DataTable from 'react-data-table';

const MyComponentHook = () => {
  const [thing, setThing] = useState();
  const handleAction = value => setThing(value);
  // unklike class methods updateState will be re-created on each render pass, therefore, make sure that callbacks passed to onRowSelected are memoized using useCallback
  const updateState = useCallback(state => console.log(state));
  const columns = useMemo(() => [
    ...
    {
      cell: () => <Button raised primary onClick={handleAction}>Action</Button>,
      ignoreRowClick: true,
      allowOverflow: true,
      button: true,
    },
    ...
  ]);

  return (
    <DataTable
      data={data}
      columns={columns}
      onRowSelected={updateState}
      selectableRows
    />
  );
}

Theming

You can override or replace the default theme using the customTheme prop. Internally, this just deep merges your theme with the default theme.

For Example:

// Override the row default height
const mySweetTheme = {
  rows: {
    height: '64px'
  }
}

class MyComponent extends Component {
  render() {
    return (
      <DataTable
        title="Arnold Movies"
        columns={columns}
        customTheme={mySweetTheme}
      />
    )
  }
);

Refer to Default Theme for reference and avaiilable properties to override

CSS Overrides

If you would like to customize the layout components of React Data Table using styled-components (e.g. styled(DataTable)), or your favorite CSS, SCSS, LESS, etc.. preprocesor you may use the following classNames:

  • rdt_Table
  • rdt_TableRow
  • rdt_TableCol
  • rdt_TableCol_Sortable
  • rdt_TableCell
  • rdt_TableHeader
  • rdt_TableFooter
  • rdt_TableHead
  • rdt_TableHeadRow
  • rdt_TableBody
  • rdt_ExpanderRow

Development

Setup

Install the latest Node JS LTS and Yarn and simply run yarn or yarn install command in the root and stories directory.

It is advised to run the script whenever NPM packages are installed.

Local development

During development,

# watch and build new source changes
yarn start
# or serve *.stories.js files and manually test on the Storybook app
yarn storybook

Including NPM packages

This project uses two package.json structure.

Library dependencies -- <root_dir>/package.json

yarn add [package-name] --dev # for dev tools
yarn add [package-name] # for app

Storybook dependencies -- <root_dir>/stories/package.json

cd stories/
yarn add [package-name]

Lint

yarn lint # runs linter to detect any style issues (css & js)
yarn lint:css # lint only css
yarn lint:js # lint only js
yarn lint:js --fix # tries to fix js lint issues

Test

yarn test # runs functional/unit tests using Jest
yarn test --coverage # with coverage

Build

yarn build # builds sources at src/