How to handle click row event on react-table - reactjs

All the examples on the internet are using old-style > component, I could not found any example for showing how to handle row_click event.
I tried my below example but no luck.
It won't be stopped by const onRowClick = (state, rowInfo, column, instance) => { as it's supposed to be.
import React from 'react'
import {useTable, useSortBy, useTableState, usePagination} from 'react-table'
function Table({getTrProps, columns, data}) {
const tableState = useTableState({pageIndex: 2})
const {
getTableProps,
getTrProps,
....
state: [{pageIndex, pageSize}],
} = useTable(
{
getTrProps,
columns,
data,
state: tableState,
},
useSortBy,
usePagination
)
// Render the UI for your table
return (
<>
<table {...getTableProps()}
className="table table-bordered"
>
<thead>
{headerGroups.map((headerGroup): any => (
<tr {...headerGroup.getHeaderGroupProps()}
className={`tx-10 tx-spacing-1 tx-color-03 tx-uppercase`}
>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>{column.render('Header')}
<span>
{(column as any).isSorted
? (column as any).isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
</table>
</>
)
}
function TransactionTable(props) {
let data = props.data || []
let filter = props.filter || {}
....
const onRowClick = (state, rowInfo, column, instance) => {
return {
onClick: e => {
debugger
}
}
}
return (
<div className={`card card-dashboard-table`}>
<div className="table-responsive">
<Table
getTrProps={onRowClick}
columns={filter.adjustParkingLot ? columns: withouParkingColumns}
data={data}/>
</div>
</div>
)
}
export default TransactionTable

I think you can pass additional props to getRowProps function on row.
You just need to pass an object containing additional props.
For example you can pass style object as this
tr.getRowProps({ style: { color: 'blue' } })
Same way you can pass onClick to getRowProps function.
You can pass the row object to your onClick callback to get the row data
I passed rowInfo callback for getting the row click
Hope this is what you want.
Ref: ReactTable RowProperties

Related

React table number of selected rows

I am trying to find out how to get the number of selected rows from react table.
Also I would like to send the number of selected rows into another sibling component, which would enable or disable based on the number of rows selected(minimum of 10).
Please Help. I would also be helpful if anyone could design a modal where I can edit a value of a selected row (only if a row is selected) in the table with value showing in edit modal.
import React, { useState } from 'react'
import { useMemo } from 'react'
import table from '../assets/json/mock.json'
import { useTable,useRowSelect, useSortBy, usePagination} from 'react-table';
import {useSticky} from 'react-table-sticky'
const Table =({columns,data})=> {
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 {
getTableProps,
getTableBodyProps,
headerGroups,
rows,
page,
nextPage,
previousPage,
canNextPage,
canPreviousPage,
pageOptions,
state,
gotoPage,
pageCount,
setPageSize,
selectedFlatRows,
prepareRow,
}=useTable({
columns,
data,
initialState : {pageIndex : 0}
},
useSortBy,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: ({ getToggleAllRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
])
}
)
const {pageIndex,pageSize,selectedRowIds}=state
return (
<>
<table className="database-table sticky" {...getTableProps()}>
<thead className='header'>
{
headerGroups.map((headerGroup)=>
(
<tr {...headerGroup.getHeaderGroupProps()}>
{
headerGroup.headers.map((column) =>
(
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render(`Header`)}
<span>
{
column.isSorted ? (column.isSortedDesc ? 'â–¼':'â–²'):''
}
</span>
</th>
))
}
</tr>
))
}
</thead>
<tbody {...getTableBodyProps()}>
{
page.map((row)=>
{
prepareRow(row)
return(
<tr {...row.getRowProps()}>
{row.cells.map((cell)=>{
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
})
}
</tbody>
</table>
<div className='header-bottom'>
{
headerGroups.map((headerGroup)=>
(
<tr {...headerGroup.getHeaderGroupProps()}>
{
headerGroup.headers.map((column) =>
(
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render(`Header`)}
<span>
{
column.isSorted ? (column.isSortedDesc ? 'â–¼':'â–²'):''
}
</span>
</th>
))
}
</tr>
))
}
</div>
<div className='table-footer'>
<div className='page-no'id='modal-item'>
Viewing : {pageIndex+1} of {pageOptions.length}
</div>
<span className ='copyright'>
© 2022 Highradius.All Rights Reserved
</span>
<span className='rowno' id='modal-item'>
Rows per Page :
<select value={pageSize} onChange={e=>setPageSize(Number(e.target.value))}>
{
[10,20,30,40,50].map(pageSize=>(
<option key={pageSize} value={pageSize}>
{pageSize }
</option>
))
}
</select>
</span>
<button button onClick={()=>previousPage()} disabled={!canPreviousPage} id='pag-btn'>{' < '}</button>
<button button onClick={()=>nextPage()} disabled={!canNextPage} id='pag-btn' >{' > '}</button>
</div>
</>
)
}
export default Table
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Add
selectedFlatRows,
state: { selectedRowIds },
after prepareRow, in const.
You can print the selected rows like this.
<p>Selected Rows: {Object.keys(selectedRowIds).length}</p>
<pre>
<code>
{JSON.stringify(
{
selectedRowIds: selectedRowIds,
'selectedFlatRows[].original': selectedFlatRows.map(d => d.original),
},
null,
2
)}
</code>
</pre>
A working example is here: https://codesandbox.io/s/naughty-pond-3e5jp

How to pass props to row in react-table

I've been following https://blog.logrocket.com/complete-guide-building-smart-data-table-react/. To apply custom styling depending on cell value, I'm updating the column object like so:
return {
Header: key,
accessor: key,
width: "150",
sortType: "basic",
Cell: ({cell: {value} }) => {
if (value == "Error") {
return <RedCell/>
}
...
}
}
Is it possible instead to apply custom styling to the row containing the cell? I guess a prop would have to be passed down to row, but am just not clear at all on how to do this.
Any pointers would be much appreciated.
For anyone stumbling upon this issue, this has been answered by the author of the react-table library here — https://spectrum.chat/react-table/general/v7-row-getrowprops-how-to-pass-custom-props-to-the-row~ff772376-0696-49d6-b259-36ef4e558821
In your Table component, you pass on any prop (say, rowProps) of your choice for rows —
<Table
columns={columns}
data={data}
rowProps={row => ({
onClick: () => alert(JSON.stringify(row.values)),
style: {
cursor: "pointer"
}
})}
/>
Then to actually use this —
function Table({ columns, data, rowProps = () => ({}) }) {
// Use the state and functions returned from useTable to build your UI
const { getTableProps, headerGroups, rows, prepareRow } = useTable({
columns,
data
});
// 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>
{rows.map(
(row, i) =>
prepareRow(row) || (
<tr {...row.getRowProps(rowProps(row))}>
{row.cells.map(cell => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
)
)}
</tbody>
</table>
);
}
Currently you are applying styling to your columns definitions.
In order to apply styling to an entire Row. (eg. make the entire row red if value == "Error"), I would do it in your table UI.
In your UI you are going to be calling prepareRow(row) and then using the row to render the cells.
maybe with : row.cells.map
At this point you could do something different based on the content of a cell row.cells[0]
maybe something like this:
{(row.cells[0].value !== "Error" && row.cells.map(cell =>
{
return (
<TableCell {...cell.getCellProps({ className: cell.column.className })}>
{cell.render('Cell')}
</TableCell>
);
})) || <RedRow />}

Component output by pressing the ReactJS button using the props

I have 2 buttons, when I click on one of them, the component will be displayed in tr className = "info". The code is made so that it is universal, if there are 50 different components, then there will be 50 different buttons. And the active button will change the class. I'm trying to do it now with the help of a props, I'm just learning, tell me, please, where is the mistake?
import React, { Component } from 'react';
import './App.css';
import Donald from '/.Donald';
import John from '/.John';
const Counter =({buttonType,id})=>{
const {
buttonType,
children,
} = props;
return (
<div
className = {`${buttonType}`}
onClick={id}
>
{children}
</div>
);
}
Counter.defaultProps={
className = 'notActive'
}
class MyName extends Component {
state = {
array:[
{id:1,component:<Donald/>, name:"My name Donald"},
{id:2,component:<John/>, name:"My name John"},
],
};
render() {
const selectedElement = this.state.array.find(item => item.id === this.state.currComponentId);
return(
<table>
<tbody>
<tr className="trButton">
{
this.state.array.map( (element) => {
return (
<Counter>
<td
className={'active'}
onClick={ () => this.element.id}
>{element.name}
</td>
</Counter>
)
})
}
</tr>
<tr className="info">
{selectedElement.component}
</tr>
</tbody>
</table>
)
}
}
export default MyName;
I see couple of issues with the code
(1) Firstly, I think you are using incorrect path for import these components i.e. Use ./Donald instead of /.Donald (notice the . before / not after), similarly './John'.
(2) Secondly you're not setting initial value for currComponentId in state. Lets set that too
state = {
array:[
{id: 1, component: <Donald/>, name:"My name Donald"},
{id: 2, component: <John/>, name:"My name John"},
],
currComponentId: 1
};
(3) Also I think you are trying to change the currComponentId in onClick. So it will be
<Counter>
<td
className={'active'}
onClick={ () => this.setState({currComponentId: element.id})}
>
{element.name}
</td>
</Counter>

Keys should be unique so that components maintain their identity across updates

I have A react component which renders a list of items that have been called from an API and set to setOfAllBooks state. Any time I search for the item, setOfAllBooks state is filtered through by the search ternm and the results are held in searchedBooks state. The results of searchedBooks are then passed to Table component and rendered in a list. At this point it works correctly, but when I search for another item it gets clustered in the Table. What I want to do is anytime I search a new Item after I have searched for a previos term I want the list-items in the Table component to be cleared to make way for the new items that have been searched.
import React, { Component } from 'react';
import './Home.css'
import axios from 'axios';
import Autosuggest from 'react-autosuggest';
var books = []
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : books.filter(book =>
book.title.toLowerCase().slice(0, inputLength) === inputValue);
};
const getSuggestionValue = suggestion => suggestion.title;
const renderSuggestion = suggestion => (
<div>
{suggestion.title}
</div>
);
const Table = ({ data }) => (
<table class="table table-hover">
<thead>
<tr class="table-primary">
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">ISBN</th>
<th scope="col">No. Of Copies</th>
</tr>
</thead>
<tbody>
{data.map(row =>
<TableRow row={row} />
)}
</tbody>
</table>
)
const TableRow = ({ row }) => (
<tr class="table-light">
<th scope="row" key={row.title}>{row.title}</th>
<td key={row.author}>{row.author}</td>
<td key={row.isbn}>{row.isbn}</td>
<td key={row.isbn}>24</td>
</tr>
)
class Home extends Component {
constructor(props) {
super(props);
this.state = {
value: '',
suggestions: [],
setOfAllBooks: [],
searchedBooks: []
};
this.searchBook = this.searchBook.bind(this);
}
componentDidMount(){
axios.get('/api/book/viewAll')
.then(res => {
this.setState({ setOfAllBooks: res.data });
books = this.state.setOfAllBooks;
console.log(this.state.setOfAllBooks)
})
}
onChange = (event, { newValue }) => {
this.setState({
value: newValue
});
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
}
searchBook(event){
event.preventDefault();
this.setState({value: this.state.value});
this.state.searchedBooks = this.state.setOfAllBooks.filter(book => book.title == this.state.value);
this.setState({searchedBook: []})
console.log(this.state.searchedBook);
}
render() {
const { value, suggestions } = this.state;
const inputProps = {
placeholder: 'Enter the name of the book',
value,
onChange: this.onChange
}
return (
<div class="form-group col-lg-4">
<label for="exampleInputEmail1">Email address</label>
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
id="searchFor"
/>
<div className=" form-group">
<label htmlFor="searchFor"> </label>
<button class="form-control btn btn-success" type="submit" onClick={this.searchBook}>Search</button>
</div>
<Table data={this.state.searchedBooks} />
</div>
)
}
}
export default Home;
The results
The Error
You need to add the key prop to the TableRow component as <TableRow key={row.title} row={row} />. Remove the key where you have right now.
.... A good rule of thumb is that elements inside the map() call need keys.
... keys used within arrays should be unique among their siblings. . Doc.
So, it seems title what you used for key will still throw warnings, as they are not uniqe. If you have ID attribute in the row object use that. Adding key to TableRow will remove the first warning, but other warning still be there until title doesn't have the uniq values across all the data.

React - mapping data to rows that are formed during a map function

I have a component called Cells which renders with data that is gotten from a flux store. My problem is that I want to render this data to a specific row but because of the way I am rendering the rows (they are dynamic so you can add them to the table), I am struggling to give the rows identifiers which the cell component can render into. I hope that makes sense!
Here is the code:
Cells Component:
import React from 'react';
export default class Cells extends React.Component {
render() {
return (
<td>{this.props.value}</td>
);
}
}
Table Component:
import React from 'react';
import TableHeader from './TableHeader.jsx';
import Cells from './Cells.jsx';
import RowForm from './RowForm.jsx';
import {createRow} from '../../actions/DALIActions';
import AppStore from '../../stores/AppStore';
export default class Table extends React.Component {
state = {rows: [], cellValues: [], isNew: false, isEditing: false};
updateState = () => this.setState({cellValues: AppStore.getCellValues()});
componentWillMount() {
AppStore.addChangeListener(this.updateState);
}
handleAddRowClickEvent = () => {
let rows = this.state.rows;
rows.push({isNew: true});
this.setState({rows: rows});
};
handleEdit = (row) => {
this.setState({isEditing: true});
};
editStop = () => {
this.setState({isEditing: false});
};
handleSubmit = (access_token, id, dataEntriesArray) => {
createRow(access_token, id, dataEntriesArray);
};
componentWillUnmount() {
AppStore.removeChangeListener(this.updateState);
}
render() {
let {rows, cellValues, isNew, isEditing} = this.state;
let headerArray = AppStore.getTable().columns;
let cells = this.state.cellValues.map((value, index) => {
return (
<Cells key={index} value={value.contents} />
);
});
return (
<div>
<div className="row" id="table-row">
<table className="table table-striped">
<thead>
<TableHeader />
</thead>
<tbody>
//////////// This is where the render of the data would happen/////////////
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{this.state.cellValues ? cells : null}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
)}
///////////////End/////////////////
</tbody>
</table>
</div>
<div className="row">
<div className="col-xs-12 de-button">
<button type="button" className="btn btn-success" onClick={this.handleAddRowClickEvent}>Add Row</button>
</div>
</div>
</div>
);
}
}
I know this isn't probably the best way to achieve what I want (any tips on that would be appreciated as well), but its what I have to work with at the moment!
Any help would be much appreciated, especially examples!
Thanks for you time!
Instead of having one object (rows) that contains row headers, and another object that contains all cells for all rows (cellvalues), I would advise you to put the cell data inside the individual row data in some way, so that your data structure would look something like this:
rows = [
{ rowID: 100, cells: [
{ cellID: 101, value: 'data' },
{ cellID: 102, value: 'data' }
]
},
{ rowID: 200, cells: [
{ cellID: 201, value: 'data' },
{ cellID: 202, value: 'data' }
]
}
]
That way, you pass the cellValues per row, and that allows you to have different cells per row.
Make a separate component for <Row>, which renders:
return
<tr>
{this.props.cells.map( cell => {
return <Cell key={cell.cellID} value={cell.value} />
})}
</tr>
And change the <tr> bit inside your main render to:
<Row key={row.keyID} cells={row.cells}/>
Finally: it is a bad idea to use the index for keys, as in key={i}. Use a key that uniquely identifies the content of the cell/ row.
UPDATE: A typical situation is that cells or rows are first created in front-end, and only get their database ID after posting to database.
Options to still get unique IDs for the keys are:
upon creation of the cell or row in react, give the cell/row a unique ID in the form of a timestamp. After getting back the response from the database, replace the timestamp with the official database key. (this is when you do optimistic updates: show new rows temporarily, until the database gives you the official rows).
do not display the row/cell, until you get response back from server with official IDs
create a unique key by hashing the content of all cell/row contents (if you are pretty sure that cell/row contents are not likely to be identical)
Hope this helps.
You need to pass row to cells render method :
{rows.map((row, index) => this.state.isEditing ?
<RowForm formKey={index} key={index} editStop={this.editStop} handleSubmit={this.handleSubmit} /> :
<tr key={index}>
{(row.cellValues || []).map((value, index) => {
return (
<Cells key={index} value={value.contents} />
)})
}
<td>
<button className="btn btn-primary" onClick={this.handleEdit.bind(this, row)}><i className="fa fa-pencil"></i>Edit</button>
</td>
</tr>
)}
Hope this help!

Resources