react table select rows - programmatically select rows based on input props - reactjs

I have created a react table with select rows following this example.
I'm trying to modify it so that when the data loads, the corresponding checkbox is either checked or unchecked based on the row data's included value. The value doesn't seem to be recognized and when I check/uncheck a row the onChange console.log event isn't being fired. What am I doing wrong.
Heres my Sandbox Example
DATA
[
{
systemId: 13,
deqId: "25007",
facilityId: 6487,
sourceId: "WS002",
sourceName: "GROVE SPRING",
flowRate: 461,
flowUom: "GPM ",
included: true
},
{
systemId: 13,
deqId: "25007",
facilityId: 4742,
sourceId: "WS004",
sourceName: "WELL #1",
flowRate: 1100,
flowUom: "GPM ",
included: true
},
{
systemId: 13,
deqId: "25007",
facilityId: 4743,
sourceId: "WS005",
sourceName: "100 W (WELL #2) ",
flowRate: 800,
flowUom: "GPM ",
included: true
},
{
systemId: 13,
deqId: "25007",
facilityId: 4744,
sourceId: "WS007",
sourceName: "NORTH (WELL #3) ",
flowRate: 900,
flowUom: "GPM ",
included: true
}
];
INDETERMINATE CHECKBOX
const IndeterminateCheckbox = React.forwardRef(
({ indeterminate, checked, name, ...rest }, ref) => {
const defaultRef = React.useRef(checked);
const resolvedRef = ref || defaultRef;
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
resolvedRef.current.checked = checked;
}, [resolvedRef, indeterminate, checked]);
return (
<>
<input
type="checkbox"
ref={resolvedRef}
checked={checked}
name={name}
id={name}
{...rest}
/>
</>
);
}
);
REACT TABLE
function ReactTable({
columns,
data,
handleCheckboxSelection,
handleCheckboxStateChange
}) {
// Use the state and functions returned from useTable to build your UI
const {
getTableProps,
getTableBodyProps,
headerGroups,
footerGroups,
rows,
prepareRow,
selectedFlatRows
} = useTable(
{
columns,
data
},
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 },
handleCheckboxStateChange
) => (
<div>
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox
name={row.original.sourceId}
onChange={(row) => console.log(row.original)} //not firing
checked={row.original.included}
{...row.getToggleRowSelectedProps()}
/>
</div>
)
},
...columns
]);
}
);
// 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()}>
{rows.slice(0, 10).map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
<tfoot>
{footerGroups.map((group) => (
<tr {...group.getFooterGroupProps()}>
{group.headers.map((column) => (
<td {...column.getFooterProps()}>
<b>{column.render("Footer")}</b>
</td>
))}
</tr>
))}
</tfoot>
</table>
<button onClick={() => handleCheckboxSelection(selectedFlatRows)}>
Save
</button>
</>
);
}
TABLE IMPLEMENTATION
const MyDataTable = ({
data
}) => {
const handleCheckboxSelection = (array) => {
console.log(array.map((d) => d.original));
};
const columns = React.useMemo(
() => [
{
Header: "Source ID",
accessor: "sourceId"
},
{
Header: "Source Name",
accessor: "sourceName"
},
{
Header: "Flow Rate (GPM)",
accessor: (d) => {
return d.flowRate ? numberWithCommas(d.flowRate) : "";
}
}
],
[]
);
return (
<ReactTable
columns={columns}
data={data}
handleCheckboxSelection={handleCheckboxSelection}
/>
);
};

The props you added to IndeterminateCheckbox are being overwritten. row.getToggleRowSelectedProps() returns an object:
{
onChange: function,
checked: Boolean,
title: String,
indeterminate: Boolean,
style: Object
}
which overwrites your properties.
The correct way to do what you want to do would be to use
initialState.selectedRowIds property from the useRowSelect API.
Map your data to their included values, then add that array to the initialState as selectedRowIds. In ReactTable.js:
const selectedRowIds = data.map((d, ind) => d.included)
const {
// etc
} = useTable(
{
columns,
data,
initialState: {selectedRowIds}
},
//etc
...columns
}
);

you have to refresh your Table.
you can use useState.
example : you can add onClick={this.mychange} to your save Button.
mychange = async () => {
this.setState({
List: //the new List Data
})
}
and dont forget the Constructor.
constructor() {
super();
this.state = {
List: anyList,
};
}

Related

How can I highlight one row in antd table with useState?

So I have a table with coordinates, and when I click on one particular row it should be highlighted and other rows should be default color.
For now it looks like this:
const TableComponent = () => {
const [active, setActive] = useState(false);
useEffect(() => {
console.log(active);
}, [active]);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={active ? "green" : null}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActive(true);
}, // click row
};
}}
/>
);
};
export default TableComponent;
When I click on one row all of the rows get highlighted, how could I make it only to one row?
You can set the active record, and compare it with the record argument of the rowClassName prop function. If they are the same, then set your custom class name to this row you clicked.
rowClassName prop has function(record, index): string signature, you should always return a string instead of null.
type ID = string | number;
const TableComponent = () => {
const [activeRecord, setActiveRecord] = useState<{ id: ID }>();
console.log(activeRecord);
return (
<Table
dataSource={dataSource}
columns={columns}
rowClassName={(record) => record.id === activeRecord?.id ? "green" : ''}
onRow={(record) => {
return {
onClick: () => {
setActiveRecord(record);
},
};
}}
/>
);
};
export default TableComponent;
antd version: v5.0.5
const App = () => {
const [activeIndex, setActiveIndex] = useState()
return (
<Table
columns={columns}
dataSource={data}
rowClassName={(record, index) => (index === activeIndex ? 'green' : null)}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
setActiveIndex(rowIndex)
}, // click row
}
}}
/>
)
}

checked checkbox remain after re-rendering

i'm building a checkbox todo List that checked checkbox is disappearing.
I have two problems.
checked checkbox remains after re-rendering
When the two checkboxes are left, they dont disappear.
Here is my codeSandBox:-----
I think this might be a setState issue, setItems([...items.filter((item) => !checkedItems[item.id])]); -> rerendering (this scope havecheckedItems ={false,true,false,false,false}) so, checkbox is remaining?
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => !checkedItems[item.id])]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[idx]}
onChange={() => checkHandler(idx)}
></input>
</div>
))}
</div>
);
}
It is not good practice to use index as key when iterating objects.
Since you have id, use this.
{items.map(todo => (
<div key={todo.id}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={checkedItems[todo.id]}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
You mess with indexes and the result is confusing.
If you want the items to be persisted and shown, just remove this line
setItems([...items.filter((item) => !checkedItems[item.id])]);
Demo
You do not need to have a separate checkedItems state. You can add a field checked in your todo object.
const todos = [
{ id: 0, value: "Wash the dishes", checked: false },
{ id: 1, value: "have lunch", checked: false },
{ id: 2, value: "listen to music", checked: false },
{ id: 3, value: "running", checked: false },
{ id: 4, value: "work out", checked: false }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const checkHandler = (idx) => {
setItems(items.filter((item) => item.id !== idx));
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
checked={todo.checked}
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
The key mistake you're making here is in onChange={() => checkHandler(idx)} and then using idx as your variable to filter out your items. idx does NOT equal the ids you have in your todo list, it will change based on the number of items left in the array.
The filter can also be improved to just be
setItems([...items.filter((item) => item.id !== idx)]);
The final code should look something like this (I'm not sure what checkedItems is meant to be doing or what it's for so it's not a consideration in this answer).
import "./styles.css";
import React from "react";
const todos = [
{ id: 0, value: "Wash the dishes" },
{ id: 1, value: "have lunch" },
{ id: 2, value: "listen to music" },
{ id: 3, value: "running" },
{ id: 4, value: "work out" }
];
export default function App() {
const [items, setItems] = React.useState(todos);
const [checkedItems, setCheckedItems] = React.useState(
new Array(todos.length).fill(false)
);
const checkHandler = (idx) => {
checkedItems[idx] = !checkedItems[idx];
setItems([...items.filter((item) => item.id !== idx)]);
setCheckedItems([...checkedItems]);
};
return (
<div className="App">
{items.map((todo, idx) => (
<div key={idx}>
<span>{todo.value}</span>
<input
type="checkbox"
onChange={() => checkHandler(todo.id)}
></input>
</div>
))}
</div>
);
}
Simply remove this line:
setItems([...items.filter((item) => !checkedItems[item.id])]);
that causes the list items to be filtered.

Updating table after delete (axios, react, express)

I'm working on a CRUD app with MySQL, React, Express, and Axios.
This is the last part but I can't seem to figure out how to refresh my table data after the record is deleted. I've tried updating the state in the .then() as well as directly after the function call. If I update the state with a different year/manager it works properly but I would like to just refresh the manager/year data that is currently active. I've never posted a code sample this long so I hope it isn't too much but I really didn't want to miss anything.
Of note, I'm using react-table so the way I handle the "only one checkbox may be selected at a time" is kind of odd.
import { useState, useEffect, useMemo, forwardRef, useRef } from "react";
import axios from "axios";
import { useSortBy, useTable, useRowSelect } from "react-table";
import "./KeeperTable.css";
const KeeperTable = (props) => {
const [playersList, setPlayersList] = useState([]);
const [url, setUrl] = useState("/getData");
const data = useMemo(() => playersList, [playersList]);
const columns = useMemo(
() => [
{
Header: "Manager",
accessor: "manager",
},
{
Header: "Player",
accessor: "player_name",
},
{
Header: "Year",
accessor: "year",
},
{
Header: "Retained",
accessor: "drafts_retained",
},
{
Header: "Remain",
accessor: "drafts_remaining",
},
],
[]
);
const IndeterminateCheckbox = forwardRef(
({ indeterminate, ...rest }, ref) => {
const defaultRef = useRef();
const resolvedRef = ref || defaultRef;
useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<>
<input
type="checkbox"
name="myCheckbox"
ref={resolvedRef}
{...rest}
/>
</>
);
}
);
const handleDeleteClick = () => {
let checkedCount = 0;
let manager = "";
let player = "";
let year = "";
//Gets the data from selected row
let rows = document.getElementsByName("myCheckbox");
for (let row of rows) {
row.checked && checkedCount++;
if (row.checked) {
manager = row.parentNode.parentNode.nextElementSibling.innerHTML;
player =
row.parentNode.parentNode.nextElementSibling.nextElementSibling
.innerHTML;
year =
row.parentNode.parentNode.nextElementSibling.nextElementSibling
.nextElementSibling.innerHTML;
}
}
//if more than one row is selected stop, else make the call to deletePlayerRecord
if (checkedCount >= 2 || checkedCount <= 0) {
console.log("only delete one record at a time!");
} else {
async function deletePlayerRecord() {
axios
.post("/deletePlayer", {
year: year,
manager: manager,
player: player,
})
.then((res) => {
setUrl(`/getData/${year}/${manager}`);
});
}
//call the function then update the state which should refresh the table. url variable has a useEffect that updates the playersList date which in turn has a
//use effect to make a new axios call and get the proper data.
console.log("clicked");
deletePlayerRecord();
}
};
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } =
useTable(
{
columns,
data,
},
useSortBy,
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => [
// Let's make a column for selection
{
id: "selection",
Header: ({ getToggleAllRowsSelectedProps }) => (
<div>
<button onClick={handleDeleteClick} className="btn-primary">
X
</button>
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
]);
}
);
useEffect(() => {
if (props.yearSearch !== "#" && props.teamSearch !== "#") {
setUrl(`/getData/${props.yearSearch}/${props.teamSearch}`);
}
}, [props.yearSearch, props.teamSearch]);
useEffect(() => {
async function getTeams() {
axios.get(url).then((res) => {
setPlayersList(res.data);
});
}
getTeams();
}, [url]);
return (
<div className="flex">
<table id="keepers" {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{rows.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
};
export default KeeperTable;
If you would like to see the server side code I can post it but I do not think it is relavent as the delete does work properly it is just the table refreshing after that I can't seem to get.

How to update quantity on basket on REACT table like below?

I have been trying without any luck, my implementation inside the Update method is not updating the state of products. Please assist with what am I doing wrong. I am using Functional Components with Hooks.
function MyTable() {
const initState = [
{ id: 1, name: "bread", quantitiy: 50, location: "cupboard" },
{ id: 2, name: "milk", quantitiy: 20, location: "fridge" },
{ id: 3, name: "water", quantitiy: 10, location: "fridge" }
];
const [state, setState] = React.useState(initState);
const handleUpdateQuantity = (productId: any, value: any) => {
let newData = data;
var index: number = newData.findIndex(
(product: any) => product.id === productId
)
if (index !== -1) {
newData[index].quantity = value
setData(newData)
} else {
console.log("Product not existing on table data...")
}
}
return (
<table>
<tr key={"header"}>
{Object.keys(state[0]).map((key) => (
<th>{key}</th>
))}
</tr>
{state.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.location}</td>
<TextField
type="number"
defaultValue={1}
onChange={(event) =>
handleUpdateQuantity(item.id, event.target.value)
}
/>
<td>{item.quantitiy}</td>
</tr>
))}
</table>
);
}
ReactDOM.render(<MyTable />, document.getElementById("target"));
You can use a functional update(as the new state depends on the previous state):
const handleUpdateQuantity = (productId: any, value: any) => {
setData(prevData => prevData.map(el => {
if(el.id === productId) {
return {...el, quantity: value};
}
return el;
}))
}
Also, quantitiys in the initial state has a typo.
and in the JSX, add value prop to the text field:
<TextField
type="number"
defaultValue={1}
value={item.quantity}
onChange={(event) =>
handleUpdateQuantity(item.id, event.target.value)
}
/>

Table - After deleting some rows, the next rows become/stay selected

When I drop some rows in my table, the next rows become/stay selected.
In my state I have selectedStudents and tableData. The selectedStudents are all selected index in my table.
When I click on delete button.. the follow code remove all rows in my tableData and all values in selectedStudents.
let diff = _.remove(this.state.tableData, (it, idx) => !~this.state.selectedStudents.indexOf(idx))
this.setState({ tableData: diff, selectedStudents: [] })
The problem is that the rows are deleted but the next rows are selected, and I don't know why
See full image with states details here
This is the full code for this component... or check in WebPack bin
export default React.createClass({
getInitialState: function() {
return {
selectedStudents: [],
tableData: [
{ name: 'John Smith' },
{ name: 'Randal White' },
{ name: 'Stephanie Sanders' },
{ name: 'Steve Brown' },
{ name: 'Joyce Whitten' },
{ name: 'Samuel Roberts' },
{ name: 'Adam Moore' }
]
}
},
// Methods
_onStudentSelected (selected) {
let selectedObjs = []
if (_.isArray(selected)) { // Multiples selections
selectedObjs.push(...selected)
} else if (_.isNumber(selected)) { // One Selection
selectedObjs.push(selected)
} else if (_.isString(selected) && selected === 'all') { // Select all elements
selectedObjs.push(...this.state.tableData.map((it, idx) => idx))
} // None selected
this.setState({ selectedStudents: selectedObjs })
},
_onClickDeleteStudent () {
let diff = _.remove(this.state.tableData, (it, idx) =>
!~this.state.selectedStudents.indexOf(idx))
this.setState({ tableData: diff, selectedStudents: [] })
},
render: function() {
return (
<Table fixedHeader multiSelectable onRowSelection={this._onStudentSelected}>
<TableBody deselectOnClickaway={false}>
{this.state.tableData.map((row, index) => (
<TableRow key={row.name} selected={this.state.selectedStudents.indexOf(index) !== -1}>
<TableRowColumn>
<TextField value={row.name} hintText='Name' />
</TableRowColumn>
<TableRowColumn>
<TextField hintText='Password' type='password' />
</TableRowColumn>
</TableRow>
))}
</TableBody>
<TableFooter adjustForCheckbox>
<TableRow>
<TableRowColumn>
<RaisedButton label='Delete' onClick={this._onClickDeleteStudent}
disabled={this.state.selectedStudents.length <= 0} />
</TableRowColumn>
</TableRow>
</TableFooter>
</Table>
);
}
});
Versions
Package | Version
----------- | ------------------------------------
material-ui | ^0.17.1
react | ^15.4.2
Browser | Chrome Version 56.0.2924.87 (64-bit)
ADDED
Was reported as a bug on issue 6496
In the current stable version 16.7, onRowSelection(indices) of Table component, indices does not reflect the right state after deleting last rows. You may checkout the fixed version via: npm install --save material-ui#next, but it is still under testing so far. Cheers.
Edited:
M.Quan changed the source code and it works okay for me by the way.
Please see https://github.com/callemall/material-ui/issues/6006
"I found the cause of the error at line 131 in
https://github.com/callemall/material-ui/blob/master/src/Table/TableBody.js
componentWillReceiveProps(nextProps) {
if (this.props.allRowsSelected !== nextProps.allRowsSelected) {
if (!nextProps.allRowsSelected) {
this.setState({
selectedRows: [],
});
} else {
this.setState({
selectedRows: this.calculatePreselectedRows(nextProps),
});
}
}
}
When i change that code with the code in version 0.16.6. It worked with me!
componentWillReceiveProps(nextProps) {
if (this.props.allRowsSelected && !nextProps.allRowsSelected) {
this.setState({
selectedRows: this.state.selectedRows.length > 0 ?
[this.state.selectedRows[this.state.selectedRows.length - 1]] : [],
});
// TODO: should else be conditional, not run any time props other than allRowsSelected change?
} else {
this.setState({
selectedRows: this.calculatePreselectedRows(nextProps),
});
}
}
"
Here a simplified modern refactored version of your example :
import React, {Component} from 'react'
import Table from 'material-ui/Table/Table'
import TableBody from 'material-ui/Table/TableBody'
import TableHeader from 'material-ui/Table/TableHeader'
import TableFooter from 'material-ui/Table/TableFooter'
import TableHeaderColumn from 'material-ui/Table/TableHeaderColumn'
import TableRow from 'material-ui/Table/TableRow'
import TableRowColumn from 'material-ui/Table/TableRowColumn'
import TextField from 'material-ui/TextField/TextField'
import RaisedButton from 'material-ui/RaisedButton/RaisedButton'
export default class MyTable extends Component {
state = {
selectedStudents: [],
tableData: [
{ id: 0, name: 'John Smith' },
{ id: 1, name: 'Randal White' },
{ id: 2, name: 'Stephanie Sanders' },
{ id: 3, name: 'Steve Brown' },
{ id: 4, name: 'Joyce Whitten' },
{ id: 5, name: 'Samuel Roberts' },
{ id: 6, name: 'Adam Moore' }
]
}
// Methods
_onStudentSelected = (selected) => {
let selectedStudents = selected === 'all'// Select all elements
? [...this.state.tableData].map(({id}) => id)
: selected
console.log('selected', selected,
'\nSelected students', selectedStudents)
this.setState({selectedStudents})
}
_onClickDeleteStudent = () => {
let tableData = this.state.tableData.filter(({id}) =>
!this.state.selectedStudents.includes(id)
)
this.setState({tableData, selectedStudents: []}, () => {
console.log('After delete', this.state.tableData,
'\nthis.state.selectedStudents', this.state.selectedStudents)
})
}
render () {
console.debug('rendered selectedStudents', this.state.selectedStudents)
const isSelected = id => {
let result = this.state.selectedStudents.includes(id)
console.log('id', id, 'result', result)
return result
}
return (
<Table fixedHeader multiSelectable enableSelectAll onRowSelection={this._onStudentSelected}>
<TableHeader>
<TableRow>
<TableHeaderColumn>Name</TableHeaderColumn>
<TableHeaderColumn>Password</TableHeaderColumn>
</TableRow>
</TableHeader>
<TableBody deselectOnClickaway={false}>
{this.state.tableData.map(({id, name}) => (
<TableRow key={id} selected={isSelected(id)}>
<TableRowColumn>
<TextField value={name} hintText='Name' />
</TableRowColumn>
<TableRowColumn>
<TextField hintText='Password' type='password' />
</TableRowColumn>
</TableRow>
))}
</TableBody>
<TableFooter adjustForCheckbox>
<TableRow>
<TableRowColumn>
<RaisedButton label='Delete' onTouchTap={this._onClickDeleteStudent}
disabled={!this.state.selectedStudents.length} />
</TableRowColumn>
</TableRow>
</TableFooter>
</Table>
)
}
}
Hope it helps despite it not solving your issue. You should raise an issue in Material-UI repo.
please use onTouchTap instead of onClick

Resources