Giter Club home page Giter Club logo

react-final-table's Introduction

React Final Table

CI License: MIT codecov minzipped-size release semantic-release

A headless UI component libray for managing complex table state in React.

Inspired by react-table but with Typescript support built in and a simpler API.

Features

  • Type safe
  • Global row filtering
  • Row selection
  • Custom column rendering
  • Column sorting
  • Data memoization for performance
  • Zero dependencies

Table of Contents

Motivation

While there is an abundance of table libraries out there to help with sorting, filtering, pagination, and more, most are opinionated about the user interface. Opinionated UIs can seem nice at first, but they quickly become limiting. To embrace the Unix philosphy of separation of concerns, the interface should be separate from the engine (from The Art of Unix Programming).

This is a minimal, type-safe, headless UI component library that you can plugin to whatever frontend you're using, as long as you're using React 16 and Hooks. You are then free to style your table any way you want while using React Final Table to manage complex state changes.

Install

npm install react-final-table

Demos

useTable

This is the main hook exposed by the library and should be your entrypoint for any table functionality. Only columns and rows are required as arguments:

const { headers, rows } = useTable(columns, rows);
  1. columns: The first argument is an array of columns of type ColumnType. Only the name of each column is required. Each column has the following type signature:
type ColumnType<T> = {
  name: string;
  label?: string;
  hidden?: boolean;
  sort?: ((a: RowType<T>, b: RowType<T>) => number) | undefined;
  render?: ({ value, row }: { value: any; row: T }) => React.ReactNode;
  headerRender?: ({ label }: { label: string }) => React.ReactNode;
};
  1. rows: Rows is the second argument to useTable and can be an array of any object type.

Examples

Basic example

import { useTable } from 'react-final-table';

const columns = [
  {
    name: 'firstName',
    label: 'First Name',
    render: ({ value }) => <h1>{value}</h1>,
  },
  {
    name: 'lastName',
    label: 'Last Name',
  },
];

const data = [
  {
    firstName: 'Frodo',
    lastName: 'Baggins',
  },
  {
    firstName: 'Samwise',
    lastName: 'Gamgee',
  },
];

const MyTable = () => {
  const { headers, rows } = useTable(columns, data);

  return (
    <table>
      <thead>
        <tr>
          {headers.map((header, idx) => (
            <th key={idx}>{header.label}</th>
          ))}
        </tr>
      </thead>
      <tbody>
        {rows.map((row, idx) => (
          <tr key={idx}>
            {row.cells.map((cell, idx) => (
              <td key={idx}>{cell.render()}</td>
            ))}
          </tr>
        ))}
      </tbody>
    </table>
  );
};

Searching

const Table: FC = () => {
    const { headers, rows, setSearchString } = useTable(
      columns,
      data,
    );

    return (
      <>
        <input
          type="text"
          onChange={e => {
            setSearchString(e.target.value);
          }}
        ></input>
        <table>
          <thead>
            <tr>
              {headers.map((header, idx) => (
                <th key={idx}>
                  {header.render()}
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {rows.map((row, idx) => (
              <tr key={idx}>
                {row.cells.map((cell, idx) => (
                  <td key={idx}>{cell.render()}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
      </>
    );

Row Selection

import React, { useMemo } from 'react';
import { useTable } from 'react-final-table';
import makeData from 'makeData'; // replace this with your own data

function App() {
  const { columns, rows } = makeData();

  const { headers, rows, selectRow, selectedRows } = useTable(
    memoColumns,
    memoData,
    {
      selectable: true,
    }
  );

  return (
    <>
      <table>
        <thead>
          <tr>
            <th></th>
            {headers.map((header, idx) => (
              <th key={idx}>{header.label}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row, idx) => (
            <tr key={idx}>
              <td>
                <input
                  type="checkbox"
                  onChange={e => {
                    selectRow(row.id);
                  }}
                />
              </td>
              {row.cells.map((cell, idx) => (
                <td key={idx}>{cell.render()}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </>
  );
}

Pagination

export const App: FC = () => {
  const memoColumns = useMemo(() => columns, []);
  const memoData = useMemo(() => data, []);

  const { headers, rows, pagination } = useTable<{
    firstName: string;
    lastName: string;
  }>(memoColumns, memoData, { pagination: true });

  return (
    <>
      <table>
        <thead>
          <tr>
            {headers.map((header, idx) => (
              <th key={idx}>{header.render()}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.map((row, idx) => (
            <tr key={idx}>
              {row.cells.map((cell, idx) => (
                <td key={idx}>{cell.render()}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
      <div>
        <button
          disabled={pagination.canPrev}
          onClick={() => pagination.prevPage()}
        >
          {'<'}
        </button>
        <button
          disabled={pagination.canNext}
          onClick={() => pagination.nextPage()}
        >
          {'>'}
        </button>
      </div>
    </>
  );
}

Performance

It's recommended that you memoize your columns and data using useMemo. This is to prevent the table from rerendering everytime your component rerenders, which can have negative consequences on performance.

Contributing

Contributing is welcome. Please read the CONTRIBUTING doc for more.

react-final-table's People

Contributors

buuntu avatar dependabot[bot] avatar dobby89 avatar mrsimb avatar stramel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

react-final-table's Issues

Enable Discussions

Please enable the Discussions feature for more free form discussions/ideas without cluttering the Issues Tab with questions/comments/ideas/discussions.

Sorting Improvements

Hello! ๐Ÿ‘‹๐Ÿผ

Just started to look into this lib and noticed some various low-hanging fruit for improvements. The first of which would be sorting.

Currently, you allow for a custom sort(compare) function to be passed in via the column. This is a great first step for allowing customization in sorting.

There are 2 things that I see that could be improved.

  1. Duplicated sorting logic in the case no custom sort is provided. This can be easily fixed by defaulting the column sort property to the default sort. ie.
const columnCompareFn = column.sort || byTextAscending((object) => object.original[action.columnName]);
  1. Currently, custom descending sorts require 2 passes over the array in order to properly sort. .sort(column.sort).reverse(). This could be implemented in a single pass by inverting the returned number. ie:
data.sort((a, b) => {
  const result = compareFn(a, b);
  return isAscending ? result : result * -1;
})

Perf test: https://jsbench.me/45kmfbx99h/1

Another thing I had thought about was providing isAscending as an option to the compareFn to allow the user to handle that as they please.

data.sort((a, b) => compareFn(a, b, isAscending))

Look forward to discussing this further and hearing your feedback @Buuntu! I'm more than happy to submit PRs for any of the changes.

/cc @cfsdfields

Unable to create columns with actions

Great library, I definitely appreciate that this has been fully written in TypeScript.

I recently tried adding an actions column to my table but was unable because the row data must have the column name present. My workaround for this involves passing a null value to the column name. While this isn't a show-stopper by any means, I think it would be more appropriate to allow column fields to not be present.

I noticed that this line is where the check for the column name is present.

throw new Error(`Invalid row data, ${column.name} not found`);

Another solution I would like to propose would involve adding a excludedColumns field that would allow us to explicitly define which columns we would like to pass the check.

const sortDataInOrder = <T extends DataType>(
  data: T[],
  columns: ColumnType<T>[],
  excludedColumns: string[],
): T[] => {
  return data.map((row: any) => {
    const newRow: any = {};
    columns.forEach(column => {
      if (!(column.name in row) && excludedColumns.indexOf(column.name) === -1) {
        throw new Error(`Invalid row data, ${column.name} not found or was not defined in excludedColumns`);
      }
      newRow[column.name] = row[column.name];
    });
    return newRow;
  });
};

I am more than happy to contribute a PR to add this functionality.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.