i am using react-table version 7. I am using global filter for filtering, i copied some code from the sandbox and integrated it into mine, this is the link from where i copied the code Open Sandbox. The problem is when ever i write something into the search bar, the whole global filter component is re-rendered and the input field loses focus and every-time, I have to click in the input field again to input text, which is very annoying as it takes time to re-render and i have to click input field everytime. why is this happening and why the same thing does not occur in the sandbox example of the react-table global filters. You can see the example on the above link i have included.
return (
<>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
{/* //rest of the code*/}
.....
</> );
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length
return (
<span>
Search:{' '}
<input
value={globalFilter || ''}
onChange={e => {
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
}}
placeholder={`${count} records...`}
style={{
fontSize: '1.1rem',
border: '0',
}}
/>
</span>
)
}
this problem does not occur if i write the code for the global filter directly inside the return statement, using this the whole component does not re-render. like this.
return(
<>
div className="row" style={{ 'zoom': '94%' }}>
<div className="col">
<div class="student-search-field">
<h4>Search: <input
value={state.globalFilter || ""}
onChange={e => {
setGlobalFilter(e.target.value || undefined); // Set undefined to remove the filter entirely
}}
placeholder={` ${count} Students..`}
style={{
fontSize: "1.1rem",
border: "1",
}}
/></h4>
</div>
</div>
</div>
{/* //rest of the code*/}
</>);
The rest of my code is like this
Hers is my complete code.
import React from 'react';
import { useLocation } from "react-router-dom";
import { useTable, usePagination, useGlobalFilter } from 'react-table';
import './classdetails.css';
function SimpleTable(props) {
const location = useLocation();
var paginationDisplay = { display: '' };
console.log(location.state);
const data = React.useMemo(
() => [
{
col1: 'john doe',
col2: "world",
},
{
col1: 'react-table',
col2: 'rocks',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
],
[],
);
const columns = React.useMemo(
() => [
{
Header: 'STUDENT NAME',
accessor: 'col1', // accessor is the "key" in the data
Cell: (row) => {
return (
<>
{row.row.original.col1}
<p>FA16-BCS-067</p>
</>);
},
},
{
Header: 'ATTENDANCE',
accessor: 'col2',// use accessor name in hidden column to hide a column e.g intitalstate.hiddenColumns['col2']
},
{
Header: 'QUIZEZ',
accessor: '',
},
{
Header: 'ASSIGNMENT',
accessor: '',
},
{
Header: 'FIRST TERM',
accessor: '',
},
{
Header: 'MID TERM',
accessor: '',
},
{
Header: 'FINAL TERM',
accessor: '',
},
{
Header: 'action',
accessor: '',
Cell: (row) => {
return (
<button className="btn btn-danger"
onClick={() => console.log(row.row)}
>
View
</button>);
},
},
],
[],
);
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length
return (
<span>
Search:{' '}
<input
value={globalFilter || ''}
onChange={e => {
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
}}
placeholder={`${count} records...`}
style={{
fontSize: '1.1rem',
border: '0',
}}
/>
</span>
)
}
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
prepareRow,
canPreviousPage,
canNextPage,
nextPage,
previousPage,
setPageSize,
pageOptions,
state,
preGlobalFilteredRows,
setGlobalFilter,
state: { pageIndex, pageSize }
} = useTable({ columns, data, initialState: { pageIndex: 0, pageSize: 5, hiddenColumns: [''] } },
useGlobalFilter, usePagination);
const count = preGlobalFilteredRows.length;
return (
<>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
{/* //table section */}
<div className="row">
<div className="col table-div-1 highlight table-2" style={{ 'overflowY': 'auto', 'height': '455px' }}>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th
{...column.getHeaderProps()}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map(row => {
prepareRow(row)
return (
<tr key={123} {...row.getRowProps()} >
{row.cells.map(cell => {
return (
<td
{...cell.getCellProps()}
onClick={() => console.log()}
>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
</div>
{/* //pagination section */}
{ props === props ? <>
<div className="row pagination" style={paginationDisplay}>
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<button className="btn btn-danger" onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{" "}
<button className="btn btn-danger" onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{" "}
<select className="btn btn-danger"
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
console.log(pageSize);
}}
>
{[5, 10, 20, 30].map(pageSize => (
<option key={pageSize.value} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</> : null }
</>
);
}
export default SimpleTable;
I have made the correction of your complete code. You did not use Table function from the Sandbox(from where you copied the code). I tested the following which is working fine and fulfil your requirement. please have a look.
import React from 'react';
import {useTable, usePagination, useGlobalFilter} from 'react-table';
import matchSorter from 'match-sorter'
function SimpleTable(props) {
var paginationDisplay = {display: ''};
const data = React.useMemo(
() => [
{
col1: 'john doe',
col2: "world",
},
{
col1: 'react-table',
col2: 'rocks',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
{
col1: 'whatever',
col2: 'you want',
},
],
[],
);
const columns = React.useMemo(
() => [
{
Header: 'STUDENT NAME',
accessor: 'col1', // accessor is the "key" in the data
Cell: (row) => {
return (
<>
{row.row.original.col1}
<p>FA16-BCS-067</p>
</>);
},
},
{
Header: 'ATTENDANCE',
accessor: 'col2',// use accessor name in hidden column to hide a column e.g intitalstate.hiddenColumns['col2']
},
{
Header: 'QUIZEZ',
accessor: '',
},
{
Header: 'ASSIGNMENT',
accessor: '',
},
{
Header: 'FIRST TERM',
accessor: '',
},
{
Header: 'MID TERM',
accessor: '',
},
{
Header: 'FINAL TERM',
accessor: '',
},
{
Header: 'action',
accessor: '',
Cell: (row) => {
return (
<button className="btn btn-danger"
onClick={() => console.log(row.row)}
>
View
</button>);
},
},
],
[],
);
function GlobalFilter({
preGlobalFilteredRows,
globalFilter,
setGlobalFilter,
}) {
const count = preGlobalFilteredRows.length
return (
<span>
Search:{' '}
<input
value={globalFilter || ''}
onChange={e => {
setGlobalFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
}}
placeholder={`${count} records...`}
style={{
fontSize: '1.1rem',
border: '0',
}}
/>
</span>
)
}
function fuzzyTextFilterFn(rows, id, filterValue) {
return matchSorter(rows, filterValue, {keys: [row => row.values[id]]})
}
// Our table component
function Table({columns, data}) {
const filterTypes = React.useMemo(
() => ({
// Add a new fuzzyTextFilterFn filter type.
fuzzyText: fuzzyTextFilterFn,
// Or, override the default text filter to use
// "startWith"
text: (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.startsWith(String(filterValue).toLowerCase())
: true
})
},
}),
[]
);
const {
getTableProps,
getTableBodyProps,
headerGroups,
page,
prepareRow,
canPreviousPage,
canNextPage,
nextPage,
previousPage,
setPageSize,
pageOptions,
state,
preGlobalFilteredRows,
setGlobalFilter,
state: {pageIndex, pageSize}
} = useTable({columns, data, initialState: {pageIndex: 0, pageSize: 5, hiddenColumns: ['']}},
useGlobalFilter, usePagination);
const count = preGlobalFilteredRows.length;
return (
<>
<GlobalFilter
preGlobalFilteredRows={preGlobalFilteredRows}
globalFilter={state.globalFilter}
setGlobalFilter={setGlobalFilter}
/>
{/* //table section */}
<div className="row">
<div className="col table-div-1 highlight table-2" style={{'overflowY': 'auto', 'height': '455px'}}>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th
{...column.getHeaderProps()}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map(row => {
prepareRow(row);
return (
<tr key={123} {...row.getRowProps()} >
{row.cells.map(cell => {
return (
<td
{...cell.getCellProps()}
onClick={() => console.log()}
>
{cell.render('Cell')}
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
</div>
{/* //pagination section */}
{props === props ? <>
<div className="row pagination" style={paginationDisplay}>
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<button className="btn btn-danger" onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>
{" "}
<button className="btn btn-danger" onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>
{" "}
<select className="btn btn-danger"
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
console.log(pageSize);
}}
>
{[5, 10, 20, 30].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</> : null}
</>
);
}
return (
<Table columns={columns} data={data}/>
)
}
export default SimpleTable;
Related
I'm using Ant Design Editable Table where there is an operation column where there is an edit button. I have 2 other columns which are date and value. So what I want to have is when I press on the edit button for a specific row, I want to be able to change the date using <DatePicker /> component from Ant Design. I have not been able to find a solution anywhere online for me. What I have right now is:
EditableTable.js
import React, { useState } from "react";
import { Table, Form, Button, DatePicker } from "antd";
import EditableCell from "./EditableCell";
const OtherLocsTable = ({ data, setData }) => {
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState("");
const isEditing = (record) => record.key === editingKey;
const edit = (record) => {
form.setFieldsValue({
date: "",
measurement: "",
...record,
});
setEditingKey(record.key);
};
const save = async (key) => {
try {
const row = await form.validateFields();
const newData = [...data];
const index = newData.findIndex((item) => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setData(newData);
setEditingKey("");
} else {
newData.push(row);
setData(newData);
setEditingKey("");
}
} catch (errInfo) {
console.log("Validate Failed:", errInfo);
}
};
const columns = [
{
title: "Date",
dataIndex: "date",
key: "date",
editable: true,
},
{
title: "Measurement",
dataIndex: "measurement",
key: "measurement",
editable: true,
},
{
title: "Operation",
dataIndex: "operation",
render: (_, record) => {
const editable = isEditing(record);
const lastElement = [...data].pop();
return editable ? (
<span>
<a
href="javascript:;"
onClick={() => save(record.key)}
style={{
marginRight: 8,
}}
>
Save
</a>
<Popconfirm title="Sure to cancel?" onConfirm={cancel}>
<a>Cancel</a>
</Popconfirm>
</span>
) : (
<>
<Typography.Link
disabled={editingKey !== ""}
onClick={() => edit(record)}
>
Edit
</Typography.Link>
{data.length > 1 ? (
record.key === lastElement.key ? (
<Typography.Link
type="danger"
disabled={editingKey !== ""}
onClick={() => deleteRow(record)}
style={{
marginLeft: 15,
}}
>
Delete
</Typography.Link>
) : null
) : null}
</>
);
},
},
];
const mergedColumns = columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
inputType:
col.dataIndex === loc
? "number"
: col.dataIndex === "date"
? "date"
: "text",
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
}),
};
});
return (
<>
<Popconfirm title="Confirm save changes?" onConfirm={handleSubmit}>
<Button
key="submit"
style={{
marginBottom: "1em",
width: "100%",
backgroundColor: "green",
}}
>
Save Changes
</Button>
</Popconfirm>
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
bordered
dataSource={data}
columns={mergedColumns}
rowClassName="editable-row"
pagination={{
onChange: cancel,
}}
rowKey="date"
/>
</Form>
</>
);
};
}
EditableCell.j
import React from "react";
import { Form, Input, InputNumber, DatePicker } from "antd";
import moment from "moment";
const EditableCell = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode =
inputType === "number" ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
<InputNumber formatter={(value) => value} parser={(value) => value} />
</Form.Item>
) : inputType === "date" ? (
<FormItem
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
initialValue={moment(record[dataIndex])}
>
<DatePicker />
</FormItem>
) : (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
<Input />
</Form.Item>
);
return <td {...restProps}>{editing ? inputNode : children}</td>;
};
export default EditableCell;
UPDATE
Currently with the code I have, I'm getting an error where it says date.clone is not a function
I found a solution
EditableTable.js
import React, { useState } from "react";
import { Table, Form, Button, DatePicker } from "antd";
import moment from 'moment';
import EditableCell from "./EditableCell";
const OtherLocsTable = ({ data, setData }) => {
const [form] = Form.useForm();
const [editingKey, setEditingKey] = useState("");
const isEditing = (record) => record.key === editingKey;
const edit = (record) => {
const {date, measurement} = record;
form.setFieldsValue({
date: (date) ? moment(date) : "",
measurement: measurement || ""
});
setEditingKey(record.key);
};
const save = async (key) => {
try {
const row = await form.validateFields();
const newData = [...data];
const index = newData.findIndex((item) => key === item.key);
if (index > -1) {
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
setData(newData);
setEditingKey("");
} else {
newData.push(row);
setData(newData);
setEditingKey("");
}
} catch (errInfo) {
console.log("Validate Failed:", errInfo);
}
};
const columns = [
{
title: "Date",
dataIndex: "date",
key: "date",
editable: true,
},
{
title: "Measurement",
dataIndex: "measurement",
key: "measurement",
editable: true,
},
{
title: "Operation",
dataIndex: "operation",
render: (_, record) => {
const editable = isEditing(record);
const lastElement = [...data].pop();
return editable ? (
<span>
<a
href="javascript:;"
onClick={() => save(record.key)}
style={{
marginRight: 8,
}}
>
Save
</a>
<Popconfirm title="Sure to cancel?" onConfirm={cancel}>
<a>Cancel</a>
</Popconfirm>
</span>
) : (
<>
<Typography.Link
disabled={editingKey !== ""}
onClick={() => edit(record)}
>
Edit
</Typography.Link>
{data.length > 1 ? (
record.key === lastElement.key ? (
<Typography.Link
type="danger"
disabled={editingKey !== ""}
onClick={() => deleteRow(record)}
style={{
marginLeft: 15,
}}
>
Delete
</Typography.Link>
) : null
) : null}
</>
);
},
},
];
const mergedColumns = columns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
inputType:
col.dataIndex === loc
? "number"
: col.dataIndex === "date"
? "date"
: "text",
dataIndex: col.dataIndex,
title: col.title,
editing: isEditing(record),
}),
};
});
return (
<>
<Popconfirm title="Confirm save changes?" onConfirm={handleSubmit}>
<Button
key="submit"
style={{
marginBottom: "1em",
width: "100%",
backgroundColor: "green",
}}
>
Save Changes
</Button>
</Popconfirm>
<Form form={form} component={false}>
<Table
components={{
body: {
cell: EditableCell,
},
}}
bordered
dataSource={data}
columns={mergedColumns}
rowClassName="editable-row"
pagination={{
onChange: cancel,
}}
rowKey="date"
/>
</Form>
</>
);
};
}
EditableCell.j
import React from "react";
import { Form, Input, InputNumber, DatePicker } from "antd";
import moment from "moment";
const EditableCell = ({
editing,
dataIndex,
title,
inputType,
record,
index,
children,
...restProps
}) => {
const inputNode =
inputType === "number" ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
<InputNumber formatter={(value) => value} parser={(value) => value} />
</Form.Item>
) : inputType === "date" ? (
<FormItem
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
<DatePicker formate="DD-MM-YYYY" />
</FormItem>
) : (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `Please Input ${title}!`,
},
]}
>
<Input />
</Form.Item>
);
return <td {...restProps}>{editing ? inputNode : children}</td>;
};
export default EditableCell;
I'm displaying user data using react-table with select all checkbox. but i want to add one more checkbox column to change active/inactive status. i have added status check box but when i select any status checkbox then 1st column checkbox also get selected.
https://codesandbox.io/s/condescending-lewin-fcukr?file=/src/App.js
import React from "react";
import styled from "styled-components";
import { useTable, usePagination, useRowSelect } from "react-table";
import makeData from "./makeData";
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
}
}
.pagination {
padding: 0.5rem;
}
`;
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<>
<input type="checkbox" ref={resolvedRef} {...rest} />
</>
);
}
);
const IndeterminateCheckbox2 = React.forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef();
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<>
<input type="checkbox" name="status" ref={resolvedRef} {...rest} />
</>
);
}
);
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page, // Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
selectedFlatRows,
state: { pageIndex, pageSize, selectedRowIds }
} = useTable(
{
columns,
data
},
usePagination,
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllPageRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllPageRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
)
},
...columns
]);
}
);
// Render the UI for your table
return (
<>
<pre>
<code>
{JSON.stringify(
{
pageIndex,
pageSize,
pageCount,
canNextPage,
canPreviousPage
},
null,
2
)}
</code>
</pre>
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"<<"}
</button>{" "}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</button>{" "}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</button>{" "}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{">>"}
</button>{" "}
<span>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span>
| Go to page:{" "}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<select
value={pageSize}
onChange={(e) => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map((pageSize) => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
<pre>
<code>
{JSON.stringify(
{
selectedRowIds: selectedRowIds,
"selectedFlatRows[].original": selectedFlatRows.map(
(d) => d.original
)
},
null,
2
)}
</code>
</pre>
</div>
</>
);
}
function App() {
const columns = React.useMemo(
() => [
{
Header: "Name",
columns: [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Last Name",
accessor: "lastName"
}
]
},
{
Header: "Info",
columns: [
{
Header: "Age",
accessor: "age"
},
{
Header: "Visits",
accessor: "visits"
},
{
Header: "Status",
accessor: "status",
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox2 {...row.getToggleRowSelectedProps()} />
</div>
)
},
{
Header: "Profile Progress",
accessor: "progress"
}
]
}
],
[]
);
const data = React.useMemo(() => makeData(100000), []);
return (
<Styles>
<Table columns={columns} data={data} />
</Styles>
);
}
export default App;
Use React If are you use Uesmemo hooks and pass in the column.
const columns = React.useMemo(
() => [
{
Header: "data",
accessor: "data",
isVisible: true,
disableFilters: true,
Cell: ({ value, row, column }) => {
if (row.index === 0) {
return (
<div>
<Input typy="check"
value={data}
onChange={(e) => {
handleChange(e, "data");
}}
disabled={false}
/>
</div>
);
} else {
return `${row.original}`;
}
},
},
i'm using the following ant component : https://ant.design/components/table/
and i'm really struggling to add the following search option in my table :
here's the table i've created so far :
i'm pretty new to both React and Typescript , since the project is not in js but in typescript i cannot get my code to work properly looking at ant documentation ; i've turned my component to a functional component as i'm gonna be using hooks and added some code to get out most of the compiler errors (type declaration for ts) , still have some syntax errors :
heres my code:
import React, { Component, useState } from 'react';
import {connect} from "react-redux";
// #ts-ignore
import Highlighter from 'react-highlight-words';
//**External Libraries */
//REACT MOSAIC
import {ExpandButton, MosaicWindow, RemoveButton} from "react-mosaic-component";
import {MosaicBranch} from "react-mosaic-component/src/types";
//BLUEPRINTJS
import {InputGroup} from "#blueprintjs/core";
//ANTDESIGN
import { Table , Button ,Tag , Input , Space} from "antd";
import 'antd/dist/antd.css'; // or 'antd/dist/antd.less'
import {CaretRightOutlined , SearchOutlined} from '#ant-design/icons';
//STYLES
import './styles.css'
//API
import {useGetAllUtenti} from '../../../api/index';
import { Utente } from '../../../api/types';
const UserSummaryWindow: React.FC<any> = (props) => {
//HOOKS STATE FOR TABLE SEARCH
const [searchText , setSearchText] = useState('');
const [searchedColumn , setSearchedColumn] = useState('');
const {path} = props;
const dataSource: Object | any = useGetAllUtenti().data;
const ruoli: any = [
{
text: 'User',
value: 'user',
},
{
text: 'Administrator',
value: 'administrator',
},
{
text: 'NumeroVerde',
value: 'numeroVerde',
},
]
const stati: any = [
{
text: 'Attivo',
value: 1,
},
{
text: 'Non Attivo',
value: 0,
}
]
const columns: any = [
{
title: 'Nome',
dataIndex: 'nome',
key: 'nome',
defaultSortOrder: 'descend',
sorter: (a:any, b:any) => { return a.nome.localeCompare(b.nome)},
},
{
title: 'Cognome',
dataIndex: 'cognome',
key: 'cognome',
sorter: (a:any, b:any) => { return a.cognome.localeCompare(b.cognome)},
},
{
title: 'Ruolo',
dataIndex: 'ruolo',
key: 'ruolo',
filters: ruoli,
onFilter: (value:any, record:any) => record.ruolo.indexOf(value) === 0,
sorter: (a:any, b:any) => { return a.ruolo.localeCompare(b.ruolo)},
render: (text: string) => (
<>
{
<Tag color={renderTagColor(text)}>
{text.toUpperCase()}
</Tag>
}
</>)
},
{
title: 'Gestore',
dataIndex: 'gestore',
key: 'gestore',
sorter: (a:any, b:any) => { return a.gestore.localeCompare(b.gestore)},
},
{
title: 'Username',
dataIndex: 'username',
key: 'username',
sorter: (a:any, b:any) => { return a.username.localeCompare(b.username)},
},
// {
// title: 'Password',
// dataIndex: 'password',
// key: 'password',
// },
// {
// title: 'IDEnte',
// dataIndex: 'idEnte',
// key: 'idEnte',
// },
{
title: 'Tipo',
dataIndex: 'tipo',
key: 'tipo',
sorter: (a:any, b:any) => a.tipo - b.tipo,
},
{
title: 'Stato',
dataIndex: 'stato',
key: 'stato',
filters: stati,
onFilter: (value:any, record:any) => record.stato.indexOf(value) === 0,
sorter: (a:any, b:any) => a.stato - b.stato,
render :(stato: number) => (
<>
{
(stato == 1) ?
(
<Tag color="#00cc00">
Attivo
</Tag>
) :
(
<Tag color="#ff0000">
Non Attivo
</Tag>
)
}
</>)
},
{
title: 'Ultimo aggiornamento password',
dataIndex: 'ultimoAggiornamentoPassword',
key: 'ultimoAggiornamentoPassword',
sorter: (a:any, b:any) => a.ultimoAggiornamentoPassword.localeCompare(b.ultimoAggiornamentoPassword)
},
{
title: '',
dataIndex: 'idUtente',
key: 'idUtente',
render: () => (
//da inserire link per andare al dettaglio / modifica utente
<Button type="primary"><CaretRightOutlined /></Button>
),
},
]
const toolbarControls = React.Children.toArray([
<ExpandButton/>,
<RemoveButton/>
]);
function renderTagColor(text:string): string {
switch(text) {
case 'user':
return 'gold';
case 'administrator':
return 'red';
case 'numeroVerde':
return 'green';
default:
return '';
}
}
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
function handleSearch (selectedKeys:any, confirm:any, dataIndex:any) {
confirm();
setSearchText(selectedKeys[0]);
setSearchedColumn(dataIndex);
};
function handleReset (clearFilters:any) {
clearFilters();
setSearchText('');
};
return (
<MosaicWindow<string>
title="Riepilogo Utenti"
path={path}
toolbarControls={toolbarControls}
>
<Table
dataSource={dataSource}
columns={columns}
bordered={true}
//pagination={{ pageSize:3,position: ['bottomCenter'] }}
pagination={{position: ['bottomCenter'] }}
rowKey={'idUtente'}
//stile per righe striped
rowClassName={(record, index) => index % 2 === 0 ? 'table-row-light' : 'table-row-dark'}
/>
</MosaicWindow>
);
};
export default UserSummaryWindow;
The part which is giving me headeache is (need to convert it to typescript [strict option in config file is enabled]):
getColumnSearchProps = dataIndex => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters }) => (
<div style={{ padding: 8 }}>
<Input
ref={node => {
this.searchInput = node;
}}
placeholder={`Search ${dataIndex}`}
value={selectedKeys[0]}
onChange={e => setSelectedKeys(e.target.value ? [e.target.value] : [])}
onPressEnter={() => this.handleSearch(selectedKeys, confirm, dataIndex)}
style={{ width: 188, marginBottom: 8, display: 'block' }}
/>
<Space>
<Button
type="primary"
onClick={() => handleSearch(selectedKeys, confirm, dataIndex)}
icon={<SearchOutlined />}
size="small"
style={{ width: 90 }}
>
Search
</Button>
<Button onClick={() => handleReset(clearFilters)} size="small" style={{ width: 90 }}>
Reset
</Button>
</Space>
</div>
),
filterIcon: filtered => <SearchOutlined style={{ color: filtered ? '#1890ff' : undefined }} />,
onFilter: (value, record) =>
record[dataIndex].toString().toLowerCase().includes(value.toLowerCase()),
onFilterDropdownVisibleChange: visible => {
if (visible) {
setTimeout(() => this.searchInput.select());
}
},
render: (text:string) =>
searchText === dataIndex ? (
<Highlighter
highlightStyle={{ backgroundColor: '#ffc069', padding: 0 }}
searchWords={[searchText]}
autoEscape
textToHighlight={text.toString()}
/>
) : (
text
),
});
I'm not here asking you to correct my code , just wanted to know if anyone could share a typescript implementation of that getColumnSearchProps function , or an example of the table component from ant design with typescript code..
Sorry for such a late reply, i was having the same problem.
I found the solution on a github repo someone made. i really wonder why the antd devs don't support typescript in these days.
anyways, here is the repo.
https://github.com/freewind-demos/typescript-react-antd-table-search-column-demo
Basically All the hardwork of moving the old JS code to typescript is done, although there are some linting errors here and there.
I hope anyone who comes looking for this answer really questions their decision to use antd instead of react-table or material-design.
Same answer as Lav Hinsu, but I'll paste the full code here, just in case that link dies.
import "antd/dist/antd.css";
import React from "react";
import ReactDOM from "react-dom";
import { SearchOutlined } from "#ant-design/icons";
import { Table, Button, Input } from "antd";
import { ColumnType } from "antd/lib/table";
function tableColumnTextFilterConfig<T>(): ColumnType<T> {
const searchInputHolder: { current: Input | null } = { current: null };
return {
filterDropdown: ({
setSelectedKeys,
selectedKeys,
confirm,
clearFilters,
}) => (
<div style={{ padding: 8 }}>
<Input
ref={(node) => {
searchInputHolder.current = node;
}}
placeholder="Search"
value={selectedKeys[0]}
onChange={(e) =>
setSelectedKeys(e.target.value ? [e.target.value] : [])
}
onPressEnter={() => confirm()}
style={{ width: 188, marginBottom: 8, display: "block" }}
/>
<Button
type="primary"
onClick={() => confirm()}
icon={<SearchOutlined />}
size="small"
style={{ width: 90, marginRight: 8 }}
>
Search
</Button>
<Button size="small" style={{ width: 90 }} onClick={clearFilters}>
Reset
</Button>
</div>
),
filterIcon: (filtered) => (
<SearchOutlined style={{ color: filtered ? "#1890ff" : undefined }} />
),
onFilterDropdownVisibleChange: (visible) => {
if (visible) {
setTimeout(() => searchInputHolder.current?.select());
}
},
};
}
type Data = {
key: string;
name: string;
};
const data: Data[] = [
{
key: "1",
name: "John Brown",
},
{
key: "2",
name: "Jim Green",
},
{
key: "3",
name: "Joe Black",
},
];
function Hello() {
return (
<div>
<Table
columns={[
{
title: "Name",
dataIndex: "name",
render: (text: string) => text,
...tableColumnTextFilterConfig<Data>(),
onFilter: (value, record) => {
return record.name
.toString()
.toLowerCase()
.includes(value.toString().toLowerCase());
},
},
]}
dataSource={data}
/>
</div>
);
}
ReactDOM.render(<Hello />, document.body);
In short, you can just extract the tableColumnTextFilterConfig function and implement the onFilter property on the desired column.
I'm trying to add "go to" first/last page buttons to the React-Table by overwriting PreviousComponent and NextComponent but it looks like both components are connected to the previous/next page functionality behaviours.
Is there any other way to add those two buttons to the default pagination component?
You can customise the text as required. I have mocked the data, you can pass the actual data as required
See the code
App.js
import React from "react";
import styled from "styled-components";
import { useTable, usePagination } from "react-table";
import makeData from "./makeData";
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
}
}
.pagination {
padding: 0.5rem;
}
`;
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page, // Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state: { pageIndex, pageSize }
} = useTable(
{
columns,
data,
initialState: { pageIndex: 2 }
},
usePagination
);
// Render the UI for your table
return (
<>
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render("Header")}</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
{/*
Pagination can be built however you'd like.
This is just a very basic UI implementation:
*/}
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{"First"}
</button>{" "}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{"<"}
</button>{" "}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{">"}
</button>{" "}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{"Last"}
</button>{" "}
<span>
Page{" "}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{" "}
</span>
<span>
| Go to page:{" "}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>{" "}
<select
value={pageSize}
onChange={e => {
setPageSize(Number(e.target.value));
}}
>
{[10, 20, 30, 40, 50].map(pageSize => (
<option key={pageSize} value={pageSize}>
Show {pageSize}
</option>
))}
</select>
</div>
</>
);
}
function App() {
const columns = React.useMemo(
() => [
{
Header: "Name",
columns: [
{
Header: "First Name",
accessor: "firstName"
},
{
Header: "Last Name",
accessor: "lastName"
}
]
},
{
Header: "Info",
columns: [
{
Header: "Age",
accessor: "age"
},
{
Header: "Visits",
accessor: "visits"
},
{
Header: "Status",
accessor: "status"
},
{
Header: "Profile Progress",
accessor: "progress"
}
]
}
],
[]
);
const data = React.useMemo(() => makeData(100000), []);
return (
<Styles>
<Table columns={columns} data={data} />
</Styles>
);
}
export default App;
Working codesandbox
For v6 I have ended up overwriting PaginationComponent
<ReactTable
....
PaginationComponent={PaginationComponent}
....
>
with modified default code as custom PaginationComponent where I have added
<div className="-first">
<button
disabled={!canPrevious}
onClick={() => {
if (!canPrevious) return
this.changePage(0)
}}
> First </button>
</div>
and
<div className="-last">
<button
onClick={() => {
if (!canNext) return
this.changePage(pages)
}}
disabled={!canNext}
> Last </button>
</div>
I am following this virtualized-rows example of react table. I know this code is using react and react table latest versions.
How can I use the react-window package with my react table version 6.10.0 and react version is 16.2.0? Also, I am loading data from backend in blocks, for example, the first 500 records, then the next 500 records and so on but the react table is visible to users with first 500 records and after this data is kept on appending, in this case, react-window FixedSizeList function is useful or VariableSizeList is useful?
Code to render React Table:
const columns = this.renderColumn();
<ReactTable
data={data}
filterable={true}
defaultFilterMethod={(filter, row) =>
matchSorter([row[filter.id]], filter.value).length !== 0
}
columns={columns}
showPagination={false}
pageSize={data.length}
minRows={0}
ref={r => {
this.selectTable = r;
}}
noDataText="No results found"
defaultSorted={[
{
id: 'travelerLastName',
desc: false,
},
]}
/>
Function to render columns:
renderColumn = () => [
{
name: '',
Header: x => {
return (
<div className={s.selectAllWrapper}>
<label className={s.checkboxContainer}>
<input
type="checkbox"
onChange={() => this.toggleSelectAll()}
checked={this.state.selectAll === 1}
ref={input => {
if (input) {
input.indeterminate = this.state.selectAll === 2;
}
}}
/>
<span className={s.checkmark} />
</label>
</div>
);
},
Cell: ({ original }) => {
return (
<div className={s.selectAllWrapper}>
<label className={s.checkboxContainer}>
<input
type="checkbox"
className="checkbox"
checked={this.state.selected[original.id] === true}
onChange={() => this.toggleRow(original.id)}
/>
<span className={s.checkmark} />
</label>
</div>
);
},
Filter: ({ filter, onChange }) => (
<div
style={{
background: 'rgba(155, 155, 155, 0.1)',
padding: '7px 5px',
fontSize: 'inherit',
fontWeight: 'normal',
outline: 'none',
height: '40px',
border: 'none',
}}
>
<SearchIcon title={''} />
</div>
),
sortable: false,
width: 45,
},
{
id: `travelerFirstName`,
Header: 'First Name',
Cell: ({ original }) => {
return (
<a
href={`/client/travelers/${original.traveler_id}?tab=profile`}
target="_blank"
>
{original.travelerFirstName}
</a>
);
},
accessor: d => d.travelerFirstName,
filterMethod: function(filter, rows) {
return filterIncludedData(
rows,
'travelerFirstName',
filter.value.toLowerCase(),
);
},
filterAll: true,
Filter: ({ filter, onChange }) => (
<input
placeholder="Search First Name"
value={filter ? filter.value : ''}
onChange={event => onChange(event.target.value)}
/>
),
className: 'left-aligned',
},
];
Some code snippets will really help.