Highlight row in one react table is highlighting the same row in a second one on the page - javascript

I have two react tables on the same page with different data. I have the option for the user to select multiple rows in both tables. The problem I am experiencing, is that if they select any row in one table, it selects the same row in the other table. How do I make it so on select it only styles the table that was selected?
Here is the getTrProps for the first table.
<ReactTable
data={this.state.table1state}
columns={columnNames}
showPagination={false}
minRows={0}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
if (this.state.selectedOpp.indexOf(rowInfo.index) >= 0) {
var selectedOpp = this.state.selectedOpp;
selectedOpp.splice(selectedOpp.indexOf(rowInfo.index), 1);
this.setState({ selectedOpp: selectedOpp });
} else {
var selectedOpp = this.state.selected;
selectedOpp.push(rowInfo.index);
this.setState({ selectedOpp: selectedOpp });
}
},
style: {
background: this.state.selectedOpp.indexOf(rowInfo.index) >= 0 ? '#00afec' : 'white',
color: this.state.selectedOpp.indexOf(rowInfo.index) >= 0 ? 'white' : 'black'
}
}
} else {
return {}
}
}}
/>
And here is for the second. Exactly the same except I am using a different state to store the selected rows in, which doesn't fix the problem.
<ReactTable
data={this.state.table2state}
columns={columnNames}
showPagination={false}
minRows={0}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
if (this.state.selected.indexOf(rowInfo.index) >= 0) {
var selected = this.state.selected;
selected.splice(selected.indexOf(rowInfo.index), 1);
this.setState({ selected: selected });
} else {
var selected = this.state.selected;
selected.push(rowInfo.index);
this.setState({ selected: selected });
}
},
style: {
background: this.state.selected.indexOf(rowInfo.index) >= 0 ? '#00afec' : 'white',
color: this.state.selected.indexOf(rowInfo.index) >= 0 ? 'white' : 'black'
}
}
} else {
return {}
}
}}
/>
Any way to specify more, like only style this specific table that this row is part of rather than that row index of all tables?
Thanks.

They are mapped to the same state of their container component. You might be passing different state variables for data but the selected state variable is the same for both React tables and so with the context being bound in the arrow functions, it's using the same parent state variable selected.
Try having different selected arrays for both the React tables.
Also, reduce code redundancy
<ReactTable
key="table1"
data={this.state.table2state}
columns={columnNames}
showPagination={false}
minRows={0}
getTrProps={(state, rowInfo) => {
const selected = this.state.selectedTable1;
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
if (this.state.selectedTable1.indexOf(rowInfo.index) >= 0) {
selected.splice(selected.indexOf(rowInfo.index), 1);
} else {
selected.push(rowInfo.index);
}
this.setState({ selectedTable1: selected });
},
style: {
background: this.state.selectedTable1.indexOf(rowInfo.index) >= 0 ? '#00afec' : 'white',
color: this.state.selectedTable1.indexOf(rowInfo.index) >= 0 ? 'white' : 'black'
}
}
} else {
return {}
}
}
}
/>

Ended up fixing this by not using the row index (rowInfo.index). Since the indexes are shared between tables, seemed to highlight both. So, instead I used the _id of the row data as follows:
<ReactTable
data={this.state.table2state}
columns={columnNames}
showPagination={false}
minRows={0}
getTrProps={(state, rowInfo) => {
if (rowInfo && rowInfo.row) {
return {
onClick: (e) => {
if (this.state.selected.indexOf(rowInfo.original._id) >= 0) {
var selected = this.state.selected;
selected.splice(selected.indexOf(rowInfo.original._id), 1);
this.setState({ selected: selected });
} else {
var selected = this.state.selected;
selected.push(rowInfo.original._id);
this.setState({ selected: selected });
}
},
style: {
background: this.state.selected.indexOf(rowInfo.original._id) >= 0 ? '#00afec' : 'white',
color: this.state.selected.indexOf(rowInfo.original._id) >= 0 ? 'white' : 'black'
}
}
} else {
return {}
}
}}
/>
This way each selection array has the id of the data in the row instead of just which row it is. Idk if you even need multiple selection arrays, prob dont, but still prob good to have them.

Related

Custom Column Searching & Filtering in Material Table

I have a column in my table called Active. Data in this column is returned as 1 or 0 for Active and Inactive.
These values are not displayed as 1 or 0. Instead, using the render prop, I have a function generateFlagText which looks for the value and renders an MUI Chip component.
Users cannot search the column by typing Active or Inactive, they would have to type 1 or 0, but they don't know this.
How can I write a check for Active or Inactive to display only those rows in the table using the customSearchAndFilter prop in Material-Table?
This is my column and function:
{
title: <Typography className={classes.colHeader}>Active</Typography>,
field: "Active",
render: (rowData) => generateFlagText(rowData.Active),
// customFilterAndSearch: (term, rowData) => console.log(term), // term is the user input
},
...
export function generateFlagText(status) {
if (status === 1)
return (
<Chip
label={"Active"}
style={{
backgroundColor: "#D9F5FD",
border: "1px solid #5CC1EE",
borderRadius: "4px",
color: "#0063B8",
}}
/>
);
if (status === 0)
return (
<Chip
label={"Inactive"}
style={{
backgroundColor: "#FFEBF5",
border: "1px solid #F69AC6",
borderRadius: "4px",
color: "#A8396F",
}}
/>
);
}
You can make something like this
const data = { type: "active" };
const rows = [
{ name: 1, active: 1 },
{ name: 2, active: 0 }
];
function f(data, rows) {
const mappedValues = {
active: 1,
inactive: 0
};
const currFilter = [];
Object.keys(mappedValues).forEach((key) => {
if (key.includes(data.type)) {
currFilter.push(mappedValues[key]);
}
});
return rows.filter((row) =>
currFilter.some((filterValue) => filterValue === row.active)
);
}
console.log(f(data, rows));
User can input any text and function return rows which includes symbols from ui table.
But it would be easier to make not an input, but a selection of two values in a selector

Problem trying conditional rendering in map() method in ReactJS

I'm having a problem where I'm using map method to create a table, then on each row of the table I have a Button that triggers conditional rendering based on state (true/false).
The problem is that all conditional renders are based on the same state this.state.displayColorPicker ? So when you click the button on one row it triggers the conditional render on all the rows.
Here is my function that renders the body of the Table :
display_table = () => {
if (!this.state.data) {
return <tr />
}
if (!this.state.data[1]) {
return <tr />
}
let xx = this.state.valuesplayers;
let i=-1;
let j=-1;
let k=500;
let l = -400;
if (typeof xx !== 'undefined' && xx !== 's') {
let xx3 = xx.map((player_row, row) =>{
i = i+1;
return (<tr className='dataRow' key={i} id={i}>{
player_row.map((player_col, col)=>{
if (this.state.data[1][col].length > 2) {
k=k+1;
return(<td key={'displayTable'+k} style={{backgroundColor: this.state.valuesplayers[row][col] === this.state.savedData[row][col] ? '' : 'yellow'}}><select key={this.state.valuesplayers[row][col]+k+0.5} value={this.state.valuesplayers[row][col]} onChange={e => this.handleChangeCell(e, row, col)}>
{this.state.data[1][col].map((DefValue, index) => {
i+=1;
if (index >= 2) {
return(<option key={'option' +i}>{DefValue}</option>);
}
return'';
})}
</select></td>)
} else if (this.state.data[1][col][0] === "Color") {
l = l+1;
const popover = {
position: 'absolute',
zIndex: '2',
top :'55px',
right:'0px',
}
const cover = {
position:'fixed',
top: '0px',
right: '0px',
bottom: '0px',
left: '0px',
}
return(<td className='SliderPicker' key={'couleur'+l} style={{backgroundColor: this.state.valuesplayers[row][col] === this.state.savedData[row][col] ? '' : 'yellow'}}>
<div className='ColorWrapper'>
<button className='colorButton' style={{borderColor: this.state.valuesplayers[row][col] === this.state.savedData[row][col] ? '' : this.state.valuesplayers[row][col],
borderWidth: this.state.valuesplayers[row][col] === this.state.savedData[row][col] ? '' : '5px' }} onClick={e => this.handleClick(e) }>Pick Color
</button>
{this.state.displayColorPicker ?
<div style={ popover }>
<div style={ cover } onClick={e => this.handleClose(e) }/>
<SketchPicker key={'ColorSelector'+l+1.5} type='color' color={this.state.valuesplayers[row][col]} value={this.state.valuesplayers[row][col]}
onChange={color => this.handleChangeColor(color, row, col)}/>
</div> : null }
</div>
</td>)
}
else {
j+=2;
return(<td key={'cellule'+j} style={{backgroundColor: this.state.valuesplayers[row][col] === this.state.savedData[row][col] ? '' : 'yellow'}}>
<input key={'inputText'+k+1.5} type="text" className='inputTable' value={this.state.valuesplayers[row][col]}
onChange={e => this.handleChangeCell(e, row, col)}/></td>)
}
})
}
<td className='ButtonRow' key={'buttonRow'+i}>
<button key={'DelRow' + i} onClick={this.delARow} className='DeleteRow'>X</button>
<button key={'AddRow' + i} onClick={this.addARow} className='AddRow'>+</button>
</td></tr>
)
})
return(xx3);
}
else {
return <td key='display table error'/>
}
}
Currently my state is declared in the constructor method :
constructor(props) {
super(props);
this.state = {
displayColorPicker: false,
};
}
If someone could help me to map a different state for each row (or any other idea that could work)
Thanks in advance.
Edit : some HandleChange functions :
handleChangeCell(e, row, col) {
let newplayers2 = this.state.valuesplayers;
newplayers2[row][col] = e.target.value;
this.setState({valuesplayers: newplayers2});
}
handleChangeColor(color, row, col) {
this.setState({color: color.hex});
console.log('color', color.hex)
let newplayers2 = this.state.valuesplayers;
newplayers2[row][col] = color.hex;
this.setState({valuesplayers: newplayers2});
}

React-data-table -Adding a CSS class to row dynamically

I am using an datatable of react-data-table-component, my table is generated from the API response data. I want to dynamically add a class to each of the rows generated based on the condition. How can I acheive this ?
https://www.npmjs.com/package/react-data-table-component
I am using the above datatable.
let columns= [
{
name: "ID",
selector: "ID",
sortable: true,
cell: row => <div>{row.ID}</div>
}];
<Datatable
columns={columns}
data={this.state.mydata} />
I want to add a custom CSS class to the entire row of this data table based on a condition.
I think you might be looking for the getTrProps callback in the table props:
getTrProps={ rowInfo => rowInfo.row.status ? 'green' : 'red' }
It's a callback to dynamically add classes or change style of a row element
Should work like this if I remember correctly:
getTrProps = (state, rowInfo, instance) => {
if (rowInfo) {
return {
className: (rowInfo.row.status == 'D') ? "status-refused" : "", // no effect
style: {
background: rowInfo.row.age > 20 ? 'red' : 'green'
}
}
}
return {};
}
render() {
<Datatable
columns={columns}
data={this.state.mydata}
getTrProps={this.getTrProps}
/>
}
example:
...
const conditionalRowStyles = [
{
when: row => row.calories < 300,
style: {
backgroundColor: 'green',
color: 'white',
'&:hover': {
cursor: 'pointer',
},
},
},
];
const MyTable = () => (
<DataTable
title="Desserts"
columns={columns}
data={data}
conditionalRowStyles={conditionalRowStyles}
/>
);
more info check here :) https://www.npmjs.com/package/react-data-table-component#conditional-row-styling

Expand row by column : Expand row by multiple columns and load different components according to the column clicked

I am following below example to implement "expand row by column".
/* eslint max-len: 0 */
import React from 'react';
import { BootstrapTable, TableHeaderColumn } from 'react-bootstrap-table';
const products = [];
function addProducts(quantity) {
const startId = products.length;
for (let i = 0; i < quantity; i++) {
const id = startId + i;
if (i < 3) {
products.push({
id: id,
name: 'Item name ' + id,
price: 2100 + i,
expand: [ {
fieldA: 'test1',
fieldB: (i + 1) * 99,
fieldC: (i + 1) * Math.random() * 100,
fieldD: '123eedd' + i
}, {
fieldA: 'test2',
fieldB: i * 99,
fieldC: i * Math.random() * 100,
fieldD: '123eedd' + i
} ]
});
} else {
products.push({
id: id,
name: 'Item name ' + id,
price: 2100 + i
});
}
}
}
addProducts(5);
class BSTable extends React.Component {
render() {
if (this.props.data) {
return (
<BootstrapTable data={ this.props.data }>
<TableHeaderColumn dataField='fieldA' isKey={ true }>Field A</TableHeaderColumn>
<TableHeaderColumn dataField='fieldB'>Field B</TableHeaderColumn>
<TableHeaderColumn dataField='fieldC'>Field C</TableHeaderColumn>
<TableHeaderColumn dataField='fieldD'>Field D</TableHeaderColumn>
</BootstrapTable>);
} else {
return (<p>?</p>);
}
}
}
export default class ExpandRow extends React.Component {
constructor(props) {
super(props);
}
isExpandableRow(row) {
if (row.id < 3) return true;
else return false;
}
expandComponent(row) {
return (
<BSTable data={ row.expand } />
);
}
render() {
const options = {
expandRowBgColor: 'rgb(242, 255, 163)',
expandBy: 'column' // Currently, available value is row and column, default is row
};
return (
<BootstrapTable data={ products }
options={ options }
expandableRow={ this.isExpandableRow }
expandComponent={ this.expandComponent }
search>
<TableHeaderColumn dataField='id' isKey={ true }>Product ID</TableHeaderColumn>
<TableHeaderColumn dataField='name' expandable={ false }>Product Name</TableHeaderColumn>
<TableHeaderColumn dataField='price' expandable={ false }>Product Price</TableHeaderColumn>
</BootstrapTable>
);
}
}
Questions:
I want to implement expand row on "multiple columns". For eg:
I would make 2 columns out of 3 to expand rows.
Product ID
Product Name
Product Price
Behavior I want is :
1.) When user clicks on "product id" column, I want to show some other BSTTableNew component like above (on expand row) but with columns "field A" & "field B" and some other functionality.
2.) When user clicks on "product name" column, I want to show BSTTable (on expand row) with similarly as above.
What changes I have to make in below code to load respective component class based upon the column I click to expand row with additional details?
I believe i have to make some changes in below code to load other components:
expandComponent(row) {
if( column === "productID") { //something of this sort I want
return (
<BSTableNew data={ row.expand } />
);
}
if( column === "productName") { //something of this sort I want
return (
<BSTable data={ row.expand } />
);
}
}
How to achieve above?
react-bootstrap-table is deprecated, you should use react-bootstrap-table2
If I correctly understood requirements, you want to expand row but the expanded content should be different, depending on column/cell clicked.
1st - expanded rows
Taking 'Expand Management' example as a base we can see how to control expanded rows using an array in the state. It's easy but we can't use onExpand handler as not column dependent. We can use expanded option:
expanded: this.state.expanded,
This array contains only expanded rows indexes.
2nd - what column clicked ?
Examples contains 'Column Event' - onClick gets column param we need. This event must be defined in columns, f.e. sth like:
const columns = [{
dataField: 'id',
text: 'Product ID',
events: {
onClick: (e, column, columnIndex, row, rowIndex) => {
console.log(row, column);
const isRowExpanded = this.state.expanded.includes( row );
this.setState( {
column: 'id',
expanded: isRowExpanded ?
this.state.expanded.filter(x => x !== row)
: [...this.state.expanded, row]
});
}
}
}, {
dataField: 'name',
text: 'Product Name',
events: {
onClick: (e, column, columnIndex, row, rowIndex) => {
console.log(row, column);
const isRowExpanded = this.state.expanded.includes( row );
this.setState( {
column: 'name',
expanded: isRowExpanded ?
this.state.expanded.filter(x => x !== row)
: [...this.state.expanded, row]
});
}
}
}, {
dataField: 'price',
text: 'Product Price'
}];
This way we have both thing handled - expanded row and last clicked column.
3rd - display the appropriate component
We need only expanded renderer parametrization:
const expandRow = {
renderer: row => {
if (this.state.column==='id') {
return (
<ColumnIDClickedTable someParam={someData[row]} />
)
} else {
return <ColumnNameClickedTable />
}
},
That should be all.
Adding condition for only first 3 rows expandable is an easy task.
All expanded rows will change the content on column change - only last click saved. Save column in indexed array if you need individual row behaviour.

How to implement filtering logic in React table?

Im using React Table to display data I receive from an API call. Currently I save the state locally and the table displays the data. I have added filtering of column values for two columns. My filtering logic is as follows:
<ReactTable
data={tableData}
noDataText="Loading.."
filterable
defaultFilterMethod={(filter, row) =>
String(row[filter.id]) === filter.value}
};
}}
columns={[
{
columns: [
{
Header: 'Name',
accessor: 'Name',
id: 'Name',
Cell: ({ value }) => (value === 'group1' ?
'group1' : 'group2'),
filterMethod: (filter, row) => {
if (filter.value === 'all') {
return true;
}
if (filter.value === 'group1') {
return row[filter.id] === 'group1';
}
if (filter.value === 'group2') {
return row[filter.id] === 'group2';
}
},
Filter: ({ filter, onChange }) =>
<select
onChange={event => onChange(event.target.value)}
style={{ width: '100%' }}
value={filter ? filter.value : 'all'}
>
<option value="all">All</option>
<option value="group1">Group1</option>
<option value="group2">Group2</option>
</select>,
},
}
As of now, the filtering rule is hard coded between two values. How to implement the filter logic so that the filtering is dynamic?(If there are 3 or 7 different values in a particular column, the dropdown should display all 7 values and filter should work based on any of the value selected). Since Im using React Table(https://react-table.js.org).
I assume you wanted to make options out of list of options.
If so, here is a way you can do it:
const exampleList = ['option1','option2','option3','option4',...,'option n']
In your column:
filterMethod: (filter, row) => customOptionsFilterMethod(filter, row),
Filter: () => ({filter, onChange}) => customOptionsFilter({filter, onChange})
Inside render() implement customOptionsFilter and customOptionsFilterMethod as:
const customOptionsFilter = ({filter, onChange}) => {
return(
<select
onChange={e => onChange(e.target.value)}
style={{ width: '100%' }}
value={filter ? filter.value : 'all'}
>
<option value='all'>Show All</option>
{
exampleList.map( k => {
return <option key={k.toString()} value={k}>{k}</option>
})
}
</select>
)
}
const customOptionsFilterMethod = (filter, row) => {
if(filter.value === '') { return true }
if(exampleList.includes(filter.value)){
return row[filter.id] === filter.value
} else { return true }
}
Haven't used the library, but it kind of sounds like something like this would work
if (filter.value === 'all') {
return true;
} else {
// what ever the value is, we will only
// show rows that have a column value
// that matches it
// If value is 'group1' just show the ones where the column
// value is 'group1', etc etc.
return row[filter.id] == filter.value;
}
Note that this will show nothing untill you get an exact match. So even if you have column values like "group1" the search term "grou" will not match it. If that's not the behaviour you want then you'd want to switch out
return row[filter.id] == filter.value;
to something like
return row[filter.id].indexOf(filter.value) >= 0;
As an alternative, if you need it to be more restricted than that, you could build your options from an array
const options = [ { value: "all" , label: "All" }, { value: "group1" , label: "Group1" }];
And use it like
<select
onChange={event => onChange(event.target.value)}
style={{ width: '100%' }}
value={filter ? filter.value : 'all'}
>
{ options.map(({value,label}) => <option value={value}>{label}</option>)}
</select>,
And in your filter logic you could check if the filter.value value is in the 'options` array.
// check if there is some (any) value that matches the criteria
// of opt.value == filter.value
return options.some(opt => opt.value == filter.value);

Categories