Sorry, but you either have no stories or none are selected somehow.
If the problem persists, check the browser console, or the terminal you've run Storybook from.
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 is giving you the best possible performance.
While React Data Table has internal optimizations to try and prevent re-renders on deeper internal components, 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 props passed into React Data Table are not re-created on every render cycle. This is ever important when you have larger data sets or you are passing complex components and columns to DataTable
.
You can typically achieve this by moving props such as objects, arrays, functions or other React components that you pass to React Data Table outside of the render
method. For cases where you need to memoize memoize-one is a great library.
The following component will cause React Data Table to fully re-render every time onSelectedRowsChange
is triggered. Why? Because 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 decision 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 because 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 MyComponent 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} onSelectedRowsChange={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} onSelectedRowsChange={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 unnecessary re-renders?
columns
function and pass the arguments neededcolumns
functionThis way, when React checks if columns
has changed columns
will instead be the cached result (remember referential equality), thus no unnecessary re-render.
Got it? Let's try this again with the optimal solution using the wonderful memoization library memoize-one
:
import React, { Component } from 'react'; import memoize from 'memoize-one'; import DataTable from 'react-data-table'; const columns = 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)} onSelectedRowsChange={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.
Optimizing Hook based components is much easier (or at least much less code). If you're building functional components in React 16.8+ 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'; function MyComponentHook() { const [thing, setThing] = useState(); const handleAction = value => setThing(value); // unlike class methods updateState will be re-created on each render pass, therefore, make sure that callbacks passed to onSelectedRowsChange are memoized using useCallback const updateState = useCallback(state => console.log(state), []); // useMemo will only be created once const columns = useMemo(() => [ ... { cell: () => <Button raised primary onClick={handleAction}>Action</Button>, ignoreRowClick: true, allowOverflow: true, button: true, }, ... ], []); return ( <DataTable data={data} columns={columns} onSelectedRowsChange={updateState} selectableRows /> ); }