Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,29 @@ const {
});
```

### `useTable` Arguments

#### `columns`

The first argument is an array of columns of type ColumnType. Each column has the following signature:

```typescript
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?: HeaderRenderType;
};
```

Only name is required, the rest are optional arguments.

#### `rows`

Rows is the second argument to useTable and can be an array of any _object_ type.

### Basic example

```tsx
Expand Down
10 changes: 6 additions & 4 deletions examples/material-ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ const columns = [
},
];

const data = [
type DataType = { first_name: string, last_name: string, date_born: string };

const data: DataType[] = [
{
first_name: 'Frodo',
last_name: 'Baggins',
Expand All @@ -69,10 +71,10 @@ function App() {
originalRows,
toggleSort,
toggleAll,
} = useTable(columns, data, {
} = useTable<DataType>(columns, data, {
selectable: true,
filter: useCallback(
(rows: RowType[]) => {
(rows: RowType<DataType>[]) => {
return rows.filter(row => {
return (
row.cells.filter(cell => {
Expand Down Expand Up @@ -107,7 +109,7 @@ function App() {
</TableCell>
{headers.map(column => (
<TableCell onClick={() => toggleSort(column.name)}>
{column.label}{' '}
{column.render()}{' '}
{column.sorted.on ? (
<>
{column.sorted.asc ? (
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.5.1",
"version": "1.1.0",
"license": "MIT",
"author": "Gabriel Abud",
"main": "dist/index.js",
Expand Down Expand Up @@ -52,10 +52,10 @@
},
"repository": {
"type": "git",
"url": "https://github.com/Buuntu/react-final-table.git"
"url": "https://github.com/Buuntu/react-final-table"
},
"bugs": {
"url": "https://github.com/Buuntu/react-final-table"
"url": "https://github.com/Buuntu/react-final-table/issues"
},
"homepage": "https://github.com/Buuntu/react-final-table#readme"
}
}
43 changes: 34 additions & 9 deletions src/hooks.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useMemo, useReducer, useEffect } from 'react';
import { useMemo, useReducer, useEffect, ReactNode } from 'react';

import {
ColumnByNamesType,
Expand All @@ -9,7 +9,9 @@ import {
UseTableReturnType,
UseTableOptionsType,
RowType,
ColumnByNameType,
HeaderType,
HeaderRenderType,
ColumnStateType,
} from './types';
import { byTextAscending, byTextDescending } from './utils';

Expand Down Expand Up @@ -173,11 +175,14 @@ export const useTable = <T extends DataType>(
data: T[],
options?: UseTableOptionsType<T>
): UseTableReturnType<T> => {
const columnsWithSorting = useMemo(
const columnsWithSorting: ColumnStateType<T>[] = useMemo(
() =>
columns.map(column => {
return {
...column,
label: column.label ? column.label : column.name,
hidden: column.hidden ? column.hidden : false,
sort: column.sort,
sorted: {
on: false,
asc: true,
Expand All @@ -187,10 +192,10 @@ export const useTable = <T extends DataType>(
[columns]
);
const columnsByName = useMemo(() => getColumnsByName(columnsWithSorting), [
columns,
columnsWithSorting,
]);

const tableData = useMemo(() => {
const tableData: RowType<T>[] = useMemo(() => {
const sortedData = sortDataInOrder(data, columnsWithSorting);

const newData = sortedData.map((row, idx) => {
Expand All @@ -205,7 +210,7 @@ export const useTable = <T extends DataType>(
hidden: columnsByName[column].hidden,
field: column,
value: value,
render: makeRender(value, columnsByName[column], row),
render: makeRender(value, columnsByName[column].render, row),
};
})
.filter(cell => !cell.hidden),
Expand All @@ -225,6 +230,18 @@ export const useTable = <T extends DataType>(
filterOn: false,
});

const headers: HeaderType<T>[] = useMemo(() => {
return [
...state.columns.map(column => {
const label = column.label ? column.label : column.name;
return {
...column,
render: makeHeaderRender(label, column.headerRender),
};
}),
];
}, [state.columns]);

useEffect(() => {
if (options && options.filter) {
dispatch({ type: 'GLOBAL_FILTER', filter: options.filter });
Expand All @@ -234,7 +251,7 @@ export const useTable = <T extends DataType>(
}, [options?.filter]);

return {
headers: state.columns.filter(column => !column.hidden),
headers: headers.filter(column => !column.hidden),
rows: state.rows,
originalRows: state.originalRows,
selectedRows: state.selectedRows,
Expand All @@ -248,10 +265,17 @@ export const useTable = <T extends DataType>(

const makeRender = <T extends DataType>(
value: any,
column: ColumnByNameType<T>,
render: (({ value, row }: { value: any; row: T }) => ReactNode) | undefined,
row: T
) => {
return column.render ? () => column.render({ row, value }) : () => value;
return render ? () => render({ row, value }) : () => value;
};

const makeHeaderRender = (
label: string,
render: HeaderRenderType | undefined
) => {
return render ? () => render({ label }) : () => label;
};

const sortDataInOrder = <T extends DataType>(
Expand All @@ -278,6 +302,7 @@ const getColumnsByName = <T extends DataType>(
const col: any = {
label: column.label,
};

if (column.render) {
col['render'] = column.render;
}
Expand Down
6 changes: 3 additions & 3 deletions src/test/selectionGlobalFiltering.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const TableWithSelection = <T extends DataType>({
<tr>
<th></th>
{headers.map((header, idx) => (
<th key={idx}>{header.label}</th>
<th key={idx}>{header.render()}</th>
))}
</tr>
</thead>
Expand Down Expand Up @@ -135,7 +135,7 @@ const TableWithFilter = <T extends DataType>({
<thead>
<tr>
{headers.map((header, idx) => (
<th key={idx}>{header.label}</th>
<th key={idx}>{header.render()}</th>
))}
</tr>
</thead>
Expand Down Expand Up @@ -217,7 +217,7 @@ const TableWithSelectionAndFiltering = <T extends DataType>({
<tr>
<th></th>
{headers.map((header, idx) => (
<th key={idx}>{header.label}</th>
<th key={idx}>{header.render()}</th>
))}
</tr>
</thead>
Expand Down
22 changes: 20 additions & 2 deletions src/test/table.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const Table = ({
<thead>
<tr>
{headers.map((header, idx) => (
<th key={idx}>{header.label}</th>
<th key={idx}>{header.render()}</th>
))}
</tr>
</thead>
Expand Down Expand Up @@ -99,12 +99,30 @@ const columnsWithRender: ColumnType<any>[] = [
},
];

test('Should see custom render HTML', () => {
test('Should see custom row render HTML', () => {
const rtl = render(<Table columns={columnsWithRender} data={data} />);

expect(rtl.getAllByTestId('first-name')).toHaveLength(2);
});

const columnsWithColRender: ColumnType<any>[] = [
{
name: 'firstName',
label: 'First Name',
headerRender: ({ label }) => <h1 data-testid="first-name">{label}</h1>,
},
{
name: 'lastName',
label: 'Last Name',
},
];

test('Should see custom column render HTML', () => {
const rtl = render(<Table columns={columnsWithColRender} data={data} />);

expect(rtl.getAllByTestId('first-name')).toHaveLength(1);
});

// to supress console error from test output
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {});
Expand Down
31 changes: 23 additions & 8 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,24 @@ export type ColumnType<T> = {
label?: string;
hidden?: boolean;
sort?: ((a: RowType<T>, b: RowType<T>) => number) | undefined;
render?: (value: any) => React.ReactNode;
render?: ({ value, row }: { value: any; row: T }) => React.ReactNode;
headerRender?: HeaderRenderType;
};

export type ColumnStateType<T> = {
name: string;
label: string;
hidden: boolean;
sort?: ((a: RowType<T>, b: RowType<T>) => number) | undefined;
sorted: {
on: boolean;
asc: boolean;
};
headerRender?: HeaderRenderType;
};

export type HeaderRenderType = ({ label }: { label: any }) => React.ReactNode;

// this is the type saved as state and returned
export type HeaderType<T> = {
name: string;
Expand All @@ -16,23 +31,23 @@ export type HeaderType<T> = {
asc: boolean;
};
sort?: ((a: RowType<T>, b: RowType<T>) => number) | undefined;
render?: (value: any) => React.ReactNode;
render: () => React.ReactNode;
};

export type DataType = { [key: string]: any };

export type ColumnByNamesType<T> = {
[key: string]: ColumnByNameType<T>;
[key: string]: ColumnType<T>;
};

export type RenderFunctionType = ({
export type RenderFunctionType<T> = ({
value,
row,
}: RenderFunctionArgsType) => React.ReactNode | undefined;
}: RenderFunctionArgsType<T>) => React.ReactNode | undefined;

type RenderFunctionArgsType = {
type RenderFunctionArgsType<T> = {
value: any;
row: Object;
row: T;
};

export type ColumnByNameType<T> = Omit<
Expand Down Expand Up @@ -93,7 +108,7 @@ export interface UseTableReturnType<T> {

export type TableState<T extends DataType> = {
columnsByName: ColumnByNamesType<T>;
columns: HeaderType<T>[];
columns: ColumnStateType<T>[];
rows: RowType<T>[];
originalRows: RowType<T>[];
selectedRows: RowType<T>[];
Expand Down