I am using django channels to update my React table. The channels work perfectly and sends update to react. With the received update, I update the react state but the table doesn't update or re-render to display added data.
I have console logged the data gotten from the channels and the React state, it updates perfectly, I have also tried using react lifecycles like shouldcomponentupdate and componentdidupdate, yet the state is updating perfectly but the table isn't
This is how I updated the state:
addMessage(message) {
console.log("message")
this.setState({
data: [...this.state.data, message]
})
}
setMessages(messages) {
console.log(messages)
this.setState({ data: messages })
}
And this is a summary of how the table looks
render(){
const {data} = this.state
console.log(data) //shows updated data at every update
return(
<table>
{data.map(n => {
return(
<tr key={n.id}>
<td> {n.name} </td>
<td> {n.found} </td>
<td> {n.date} </td>
</tr>
)})}
</table>
)}
This is the full component as requestd(it's too long that was why I minimized it)
class EnhancedTable extends React.Component {
constructor(props) {
super(props)
this.state = {
order: 'asc',
orderBy: 'calories',
selected: [],
data:[],
message: [],
messages: [],
page: 0,
rowsPerPage: 50,
expanded: false,
search:''
};
this.waitForSocketConnection(() => {
WebSocketInstance.addCallbacks(this.setMessages.bind(this), this.addMessage.bind(this));
WebSocketInstance.newChatMessage(this.props.message)
WebSocketInstance.fetchMessages()
})
}
waitForSocketConnection(callback) {
const component = this;
setTimeout(
function () {
if (WebSocketInstance.state() === 1) {
console.log('connection is secure');
callback()
return;
} else {
console.log('waiting for connection')
component.waitForSocketConnection(callback)
}
}, 100);
}
addMessage(message) {
console.log("message")
let newData = [...this.state.data, message]
this.setState({
data: newData
})
}
setMessages(messages) {
console.log(messages)
this.setState({ data: messages })
}
componentDidMount() {
WebSocketInstance.connect()
}
handleRequestSort = (event, property) => {
const orderBy = property;
let order = 'desc';
if (this.state.orderBy === property && this.state.order === 'desc') {
order = 'asc';
}
this.setState({ order, orderBy });
};
handleSelectAllClick = event => {
if (event.target.checked) {
this.setState(state => ({ selected: state.data.map(n => n.id) }))
return;
}
this.setState({ selected: [] });
};
handleAddClick = () => event => {
const selected = this.state.selected
selected.forEach(function (element) {
axios.put(`${process.env.REACT_APP_DEV_API_URL}/api/main/${element}/`, {
relevance: true
})
.then((response) =>
console.log(response)
)
.catch(function (error) {
console.log(error)
})
}
)
}
handleClick = (event, id) => {
const { selected } = this.state;
const selectedIndex = selected.indexOf(id);
let newSelected = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
this.setState({ selected: newSelected });
};
handleChangePage = (event, page) => {
this.setState({ page });
};
handleChangeRowsPerPage = event => {
this.setState({ rowsPerPage: event.target.value });
};
handleChange = panel => (event, expanded) => {
this.setState({
expanded: expanded ? panel : false,
});
};
isSelected = id => this.state.selected.indexOf(id) !== -1;
render() {
const { classes } = this.props;
const { order, orderBy, selected, rowsPerPage, page, data, expanded } = this.state;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
// const { expanded } = this.state;
console.log(data)
return (
<Paper className={classes.root}>
<EnhancedTableToolbar numSelected={selected.length} handleAddClick={this.handleAddClick()} />
<div className={classes.tableWrapper}>
<Table className={classes.table} aria-labelledby="tableTitle">
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={this.handleSelectAllClick}
onRequestSort={this.handleRequestSort}
rowCount={data.length}
/>
<TableBody>
{stableSort(data, getSorting(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(n => {
const isSelected = this.isSelected(n.id);
return (
<TableRow
hover
key={n.id}
>
<TableCell padding="checkbox" onClick={event => this.handleClick(event, n.id)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}>
<Checkbox checked={n.relevance === true ? true : false} classes={{
root: classes.root,
}} />
</TableCell>
<TableCell component="th" scope="row" padding="none" style={{ color: "#1565C0" }}>
<a href={`${n.url}`} target="_blank" rel='noopener noreferrer'>{expanded === false ? `${n.url.slice(0, 30)}` : `${n.url}`}</a>
</TableCell>
<TableCell component="th" scope="row" padding="none">{n.keyword}</TableCell>
<TableCell component="th" scope="row" padding="none"><ExpansionPanel
square
expanded={expanded === `${n.id}`}
onChange={this.handleChange(`${n.id}`)}
>
<ExpansionPanelSummary>
<Typography style={{ color: `${n.keyword ? 'red' : 'none'}` }}>{n.source.slice(1, 100)}...</Typography> <Button color="primary" style={{ padding: '0px' }}>{expanded === false ? 'Read More' : 'Read Less'}</Button>
</ExpansionPanelSummary>
<ExpansionPanelDetails>
<div>
{n.source.split(',').map(element => {
return (<Typography key={Math.random()} >{element.replace(n.keyword, n.keyword.toUpperCase())}</Typography>)
})}
</div>
</ExpansionPanelDetails>
</ExpansionPanel>
</TableCell>
<TableCell align="right">{n.found}</TableCell>
<TableCell align="right">
{n.severityLevel === 'low' ?
<Badge className={classes.margin} badgeContent={"Low"} color="primary">
<CloudCircle />
</Badge>
:
<Badge className={classes.margin} badgeContent={"High"} color="secondary">
<AddCircleIcon />
</Badge>}
</TableCell>
<TableCell align="right" style={{ color: "#2E7D32" }}>{n.timestamp}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
'aria-label': 'Previous Page',
}}
nextIconButtonProps={{
'aria-label': 'Next Page',
}}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</Paper>
);
}
}
EnhancedTable.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(EnhancedTable);
I expected as the state updates, the table updates and shows updated data.
Are you using the key prop on your loops children? When you make loops in your render function you need to provide the key prop to the loops child so React knows what components to update.
So in your case it would be something like this: {data.map(n => <Component key={n.id} />)} if n is an object with an unique property id.
React key prop: https://reactjs.org/docs/lists-and-keys.html#keys
I was able to fix this using redux and react lifecycle method,
shouldComponentUpdate(nextProps, nextState){
if (this.state.data !== nextState.data){
this.setState({
data: nextState.data
})
return true
}
return false
}
This compares the current state and the next state.
Related
I want to pass a callback function in a prop of type object. I have created a custom component for data table in that component I am passing columns, data and actions as props.
actions props is an array of objects with handler callback function linked to each element.
Here is my Data table component code
const DataTable = ({ columns, data, actions }) => {
return <>
<TableContainer>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>#</TableCell>
{
columns.map((item) => {
return (<TableCell key={item.key}>{item.label}</TableCell>);
})
}
{
actions && <TableCell>Actions</TableCell>
}
</TableRow>
</TableHead>
<TableBody>
{data.map((row, index) => (
<TableRow
key={row.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>
{index + 1}
</TableCell>
{
columns.map(item => {
return (<TableCell key={item.key}>{row[item.key] ? row[item.key] : ''}</TableCell>);
})
}
<TableCell>
{
actions.map((element) => {
return (<React.Fragment key={element.label}><span onClick={(row) => {element.handler(row.id)}}>{element.icon}</span></React.Fragment>)
})
}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Pagination count={1} />
</TableContainer>
</>
}
export default DataTable;
and here is code of using that data table
const [data, setData] = useState([]);
const columns = [
{
key: "provider",
label: "Provider",
},
{
key: "phone_number",
label: "Phone Number",
},
{
key: "status",
label: "Status",
},
];
const actions = [
{
icon: <VisibilityIcon sx={{ color: '#1976d2', cursor: 'pointer' }} />,
label: 'View',
handler: ''
},
{
icon: <DeleteIcon sx={{ color: '#1976d2', marginLeft: '4px', cursor: 'pointer' }} />,
label: 'Delete',
handler: deleteHandler
}
];
// Fetching data from api
const deleteHandler = async (id) => {
// Calling delete api
}
return <>
<Card>
<CardContent>
<DataTable columns={columns} data={data} actions={actions} />
</CardContent>
</Card></>;
Now on clicking on any action button I am getting this error
TypeError: element.handler is not a function
Is there any specific way using which i can pass these callback function inside prop object.
You have to check if the element.handelr is a function or not
// in your child component
const actionFn = (id, handler) => {
if (typeof handler !== 'function' ) {
return;
}
handler(row.id);
}
// JSX
<TableCell>
{
actions.map((element) => {
return <React.Fragment key={element.label}>
<span onClick={(row) => {actionFn(row.id, element.handler)}}>{element.icon}</span>
</React.Fragment>
})
}
</TableCell>
Also move const deleteHandler above const actions
Okay so I have a really strange error in my react application.
Basically I have a function that handles an input change and changes the state from the parent component.
import React from "react";
import PaginationExtended from "../PaginationExtended/PaginationExtended";
import { ScreenContext } from "../../helpers/context";
import {
Paper,
TableCell,
TableContainer,
Table,
TableBody,
TableHead,
TableRow,
Checkbox,
TextField,
} from "#material-ui/core";
import EnhancedTableToolbar from "../EnhancedToolBar/EnhancedToolBar";
import uuid from "react-uuid";
import findWithAttr from "../../helpers/findAttr";
import objectInArray from "../../helpers/objectInArray";
import ItemTableRow from "./ItemTableRow/ItemTableRow";
const ItemTable = (props) => {
const screen = React.useContext(ScreenContext);
const handleCheckboxClick = (e) => {
if (e.target.checked) {
props.setDynamicRows((prevState) => {
const newTableData = [...prevState];
console.log(newTableData);
const rowIndex = findWithAttr(props.rows, "id", parseInt(e.target.id));
const newRow = {};
props.headings.forEach((heading) => {
newRow[heading.text.toLowerCase()] =
props.rows[rowIndex][heading.default];
});
newRow.id = e.target.id;
newRow.type = "accepted";
newRow.files = [];
newRow.category = props.title;
newTableData.push(newRow);
return newTableData;
});
} else {
props.setDynamicRows((prevState) => {
const newTableData = [...prevState];
console.log(newTableData);
const rowIndex = findWithAttr(
newTableData,
"id",
parseInt(e.target.id)
);
newTableData.splice(rowIndex, 1);
return newTableData;
});
}
};
const handleInputChange = () => {
console.log(1);
props.setDynamicRows((prevState) => {
const newTableData = [...prevState];
console.log(newTableData);
const rowIndex = findWithAttr(newTableData, "id", props.row.id);
newTableData[rowIndex][e.target.name] = e.target.value;
return newTableData;
});
};
return (
<Paper className={`${props.className}`}>
<EnhancedTableToolbar
screen={screen}
title={props.title}
// record={props.rows}
// setRows={setRows}
/>
<TableContainer>
<Table aria-label="simple table">
<TableHead>
<TableRow>
{props.hasCheckbox && <TableCell padding="checkbox" />}
{props.headings.map((heading) => {
return (
<TableCell className={"text-nowrap"} align="right">
{heading.text}
</TableCell>
);
})}
</TableRow>
</TableHead>
<TableBody>
{props.rows.map((row, index) => {
if (!props.inputTable) {
return (
<ItemTableRow
title={props.title}
headings={props.headings}
handleCheckboxClick={handleCheckboxClick}
dynamicRows={props.dynamicRows}
index={index}
row={row}
hasCheckbox={props.hasCheckbox}
setDynamicRows={props.setDynamicRows}
/>
);
} else {
if (row.type === "accepted" && row.category === props.title) {
return (
<ItemTableRow
title={props.title}
headings={props.headings}
handleCheckboxClick={handleCheckboxClick}
dynamicRows={props.dynamicRows}
index={index}
row={row}
hasCheckbox={props.hasCheckbox}
handleInputChange={handleInputChange}
/>
);
}
}
})}
</TableBody>
</Table>
</TableContainer>
{props.hasPagination && (
<PaginationExtended
recordsPerPage={rowsPerPage}
className={"p-3 justify-content-end"}
totalRecords={rows.length}
paginate={handleChangePage}
currentPage={page}
shape={"round"}
/>
)}
</Paper>
);
};
export default ItemTable;
This function is called from a child component of the component at the top.
import React from "react";
import { TableCell, TableRow, Checkbox, TextField } from "#material-ui/core";
import objectInArray from "../../../helpers/objectInArray";
const ItemTableRow = (props) => {
return (
<TableRow key={props.index}>
{props.hasCheckbox && (
<TableCell padding="checkbox">
<Checkbox
id={props.row.id}
checked={objectInArray(
props.dynamicRows,
"id",
props.row.id.toString()
)}
onClick={props.handleCheckboxClick}
// inputProps={{
// "aria-labelledby": `${props.title}-${index}`,
// }}
/>
</TableCell>
)}
{props.headings.map((heading) => {
return (
<TableCell
className={"text-nowrap"}
id={`${props.title}-${props.index}`}
align="right"
>
{heading.isTextInput ? (
<TextField
onChange={props.handleInputChange}
id={`${props.title}-${heading.text}-${props.row.id}`}
type={heading.inputType}
size={"small"}
variant="outlined"
inputProps={{ min: 0 }}
notched={false}
InputLabelProps={{ shrink: false }}
className={`minInputWidth`}
InputProps={{
classes: { input: "py-2 customize-inputField" },
}}
name={heading.text.toLowerCase()}
placeholder={heading.text}
/>
) : (
<p>{props.row[heading.default]}</p>
)}
</TableCell>
);
})}
</TableRow>
);
};
export default ItemTableRow;
When I trigger this event the following error appears in the console.
Uncaught TypeError: props.setDynamicRows is not a function
The curious thing about this is that the handleCheckboxClick function does not throw any error and is also called called from the child component. I have tried reestructuring the components and functions but nothing works.
If someone knows what is happening I would really appreciate the help, thanks.
I have a table that has clickable row headers that would allow the user to sort the table based on the onRequestSort function which you would see below. However this currently is not working correctly, I am using reactHooks to do the handleRequestSort and I may have made a mistake along the way, any advice would be great. Below is my suppliers component
export const desc = (a, b, orderBy) => {
if (b[orderBy] < a[orderBy]) {
return -1;
}
else if (b[orderBy] > a[orderBy]) {
return 1;
}
else {
return 0;
}
};
export const stableSort = (array, cmp) => {
if (array) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = cmp(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map(el => el[0]);
}
else return [];
};
export const getSorting = (order, orderBy) => {
return order === 'asc' ? (a, b) => desc(a, b, orderBy) : (a, b) => -
desc(a, b, orderBy);
};
const SuppliersTable = (props) => {
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(5);
const [order, setOrder] = useState('desc');
const [orderBy, setOrderBy] = useState('');
const handleChangePage = (event, page) => {
setPage({ page });
};
const handleChangeRowsPerPage = event => {
setRowsPerPage({ rowsPerPage: event.target.value });
};
const handleRequestSort = (event, property) => {
const orderBy = property;
let order = 'desc';
if (orderBy === property && order === 'desc') {
order = 'asc';
}
setOrder('desc');
setOrderBy('asc');
};
const handleClick = (id) => {
props.history.push(`/suppliers/${id}/details`);
};
const { classes, filteredSuppliers } = props;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, filteredSuppliers.length - page * rowsPerPage);
return (
<Paper className={classes.root}>
<div className={classes.tableWrapper}>
<Table className={classes.table} aria-label="tableTitle" id="suppliersTable">
<SuppliersTableHead
order={order}
orderBy={orderBy}
onRequestSort={handleRequestSort}
rowCount={filteredSuppliers.length}
/>
<TableBody aria-label="suppliers Table">
{stableSort(filteredSuppliers, getSorting(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(supplier => {
return (
supplier.addressInfo.map(row => (
<TableRow className={classes.tableRow}
hover={true}
// onClick={() => { handleClick(supplier.id); }} for a later ticket
tabIndex={0}
key={row.id}
>
<TableCell component="th" scope="row" padding="none">{supplier.name}</TableCell>
<TableCell padding="none">{row.officeName}</TableCell>
<TableCell padding="none">{row.officeEmail}</TableCell>
<TableCell padding="none">{row.officeTelephone}</TableCell>
<TableCell padding="none">{row.town}</TableCell>
<TableCell padding="none">{row.county}</TableCell>
<TableCell padding="none">{row.postcode}</TableCell>
</TableRow>
))
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={8} />
</TableRow>
)}
</TableBody>
</Table>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={filteredSuppliers.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
'aria-label': 'Previous Page',
}}
nextIconButtonProps={{
'aria-label': 'Next Page',
}}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</div>
</Paper>
);
};
and my table head component
const rows = [
{ id: 'title', numeric: false, disablePadding: true, label: 'Title' },
{ id: 'office', numeric: false, disablePadding: true, label: 'Office' },
{ id: 'email', numeric: false, disablePadding: true, label: 'Email' },
{ id: 'telephone', numeric: false, disablePadding: true, label: 'Telephone' },
{ id: 'town', numeric: false, disablePadding: true, label: 'Town' },
{ id: 'county', numeric: false, disablePadding: true, label: 'County' },
{ id: 'postCode', numeric: false, disablePadding: true, label: 'Post Code' },
];
const SuppliersTableHead = props => {
const createSortHandler = property => {
return event => {
props.onRequestSort(event, property);
};
};
const { order, orderBy } = props;
return (
<TableHead>
<TableRow>
{rows.map(row => (
<TableCell
key={row.id}
align={row.numeric ? 'right' : 'left'}
padding={row.disablePadding ? 'none' : 'default'}
sortDirection={orderBy === row.id ? order : false}
>
<Tooltip
title="Sort"
placement={row.numeric ? 'bottom-end' : 'bottom-start'}
enterDelay={300}
>
<TableSortLabel
active={orderBy === row.id}
direction={order}
onClick={createSortHandler(row.id)}
>
{row.label}
</TableSortLabel>
</Tooltip>
</TableCell>
))}
</TableRow>
</TableHead>
);
};
SuppliersTableHead.propTypes = {
onRequestSort: PropTypes.func.isRequired,
order: PropTypes.string.isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired,
};
It looks like you need to pass your orderBy:
<SuppliersTableHead
order={order}
orderBy={orderBy}
onRequestSort={()=>handleRequestSort(orderBy)}
rowCount={filteredSuppliers.length}
/>
const handleRequestSort = (orderBy) => {
// update your sort here
}
I am new to react and javascript. May I know how to call other class function from current class?
I only able to use prop to pass the data between class.
For example, I have one toolbar and one user class. When I click the toolbar delete button. I want to call delete function in my user class. How to achieve this?
So this is my toolbar. When I click delete button,I want to trigger deleteUser in my user class.
let EnhancedTableToolbar = props => {
const { numSelected, classes, selectedArray } = props;
return (
<Toolbar
className={classNames(classes.root, {
[classes.highlight]: numSelected > 0,
})}
>
<div className={classes.title}>
{numSelected > 0 ? (
<Typography color="inherit" variant="subtitle1">
{numSelected} selected
</Typography>
) : (
<Typography variant="h6" id="tableTitle">
User List
</Typography>
)}
</div>
<div className={classes.spacer} />
<div className={classes.actions}>
{numSelected > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="Delete">
<DeleteIcon onClick={() => { if (window.confirm('Are you sure you wish to delete '+numSelected +' item?')) deleteUser() } }>
</DeleteIcon>
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton aria-label="Filter list">
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</div>
</Toolbar>
);
};
EnhancedTableToolbar.propTypes = {
classes: PropTypes.object.isRequired,
numSelected: PropTypes.number.isRequired,
selectedArray: PropTypes.array.isRequired,
};
EnhancedTableToolbar = withStyles(toolbarStyles)(EnhancedTableToolbar);
This is my user class. The toolbar is used in this class. I want the deleteUser() get triggered when the toolbar delete button is clicked.
class User extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
reset() {
this.setState(initialState);
}
componentDidMount() {
axios.get("http://localhost:4000/readUsers")
.then(json => {
this.setState({data: json.data});
console.log({json})
}).catch(err => console.log("error: "+err));
}
deleteUser(){
}
displayUsers(){
const { classes } = this.props;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
console.log("selected length: "+selected.length+" selected: "+selected);
return(
<Paper className={classes.root}>
<EnhancedTableToolbar numSelected={selected.length} selectedArray={selected} />
<div className={classes.tableWrapper}>
<Table className={classes.table} aria-labelledby="tableTitle">
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={this.handleSelectAllClick}
onRequestSort={this.handleRequestSort}
rowCount={data.length}
/>
<TableBody>
{stableSort(data, getSorting(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(n => {
const isSelected = this.isSelected(n.id);
return (
<TableRow
hover
onClick={event => this.handleClick(event, n.id)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={isSelected} />
</TableCell>
<TableCell component="th" scope="row" padding="none">
{n.id}
</TableCell>
<TableCell align="right">{n.name}</TableCell>
<TableCell align="right">{n.username}</TableCell>
<TableCell align="right">{n.email}</TableCell>
<TableCell align="right">{n.address}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
'aria-label': 'Previous Page',
}}
nextIconButtonProps={{
'aria-label': 'Next Page',
}}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</Paper>
);
}
handleRequestSort = (event, property) => {
const orderBy = property;
let order = 'desc';
if (this.state.orderBy === property && this.state.order === 'desc') {
order = 'asc';
}
this.setState({ order, orderBy });
};
handleSelectAllClick = event => {
if (event.target.checked) {
this.setState(state => ({ selected: state.data.map(n => n.id) }));
return;
}
this.setState({ selected: [] });
};
handleClick = (event, id) => {
const { selected } = this.state;
const selectedIndex = selected.indexOf(id);
let newSelected = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
this.setState({ selected: newSelected });
};
handleChangePage = (event, page) => {
this.setState({ page });
};
handleChangeRowsPerPage = event => {
this.setState({ rowsPerPage: event.target.value });
};
isSelected = id => this.state.selected.indexOf(id) !== -1;
render() {
const { classes } = this.props;
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
{this.checkCurrentButton()}
<Card>
<CardHeader color="primary">
<h4 className={classes.cardTitleWhite}>User List</h4>
<p className={classes.cardCategoryWhite}>
{}
</p>
</CardHeader>
<CardBody>
{this.displayUser()}
</CardBody>
<CardFooter>
</CardFooter>
</Card>
</GridItem>
</GridContainer>
);
}
}
User.propTypes = {
classes: PropTypes.object.isRequired,
};
You can just pass the function as a prop to the toolbar from your parent class and then access it like any other prop with this.props.deleteUser() like so:
let EnhancedTableToolbar = props => {
const { numSelected, classes, selectedArray } = props;
return (
<Toolbar
className={classNames(classes.root, {
[classes.highlight]: numSelected > 0,
})}
>
<div className={classes.title}>
{numSelected > 0 ? (
<Typography color="inherit" variant="subtitle1">
{numSelected} selected
</Typography>
) : (
<Typography variant="h6" id="tableTitle">
User List
</Typography>
)}
</div>
<div className={classes.spacer} />
<div className={classes.actions}>
{numSelected > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="Delete">
<DeleteIcon onClick={() => { if (window.confirm('Are you sure you wish to delete '+numSelected +' item?')) this.props.deleteUser() } }>
</DeleteIcon>
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton aria-label="Filter list">
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</div>
</Toolbar>
);
};
EnhancedTableToolbar.propTypes = {
classes: PropTypes.object.isRequired,
numSelected: PropTypes.number.isRequired,
selectedArray: PropTypes.array.isRequired,
};
EnhancedTableToolbar = withStyles(toolbarStyles)(EnhancedTableToolbar);
And your User Class:
class User extends React.Component {
constructor(props) {
super(props);
this.state = initialState;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
reset() {
this.setState(initialState);
}
componentDidMount() {
axios.get("http://localhost:4000/readUsers")
.then(json => {
this.setState({data: json.data});
console.log({json})
}).catch(err => console.log("error: "+err));
}
deleteUser(){
}
displayUsers(){
const { classes } = this.props;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
console.log("selected length: "+selected.length+" selected: "+selected);
return(
<Paper className={classes.root}>
<EnhancedTableToolbar deleteUser={this.deleteUser} numSelected={selected.length} selectedArray={selected} />
<div className={classes.tableWrapper}>
<Table className={classes.table} aria-labelledby="tableTitle">
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={this.handleSelectAllClick}
onRequestSort={this.handleRequestSort}
rowCount={data.length}
/>
<TableBody>
{stableSort(data, getSorting(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(n => {
const isSelected = this.isSelected(n.id);
return (
<TableRow
hover
onClick={event => this.handleClick(event, n.id)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.id}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox checked={isSelected} />
</TableCell>
<TableCell component="th" scope="row" padding="none">
{n.id}
</TableCell>
<TableCell align="right">{n.name}</TableCell>
<TableCell align="right">{n.username}</TableCell>
<TableCell align="right">{n.email}</TableCell>
<TableCell align="right">{n.address}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={data.length}
rowsPerPage={rowsPerPage}
page={page}
backIconButtonProps={{
'aria-label': 'Previous Page',
}}
nextIconButtonProps={{
'aria-label': 'Next Page',
}}
onChangePage={this.handleChangePage}
onChangeRowsPerPage={this.handleChangeRowsPerPage}
/>
</Paper>
);
}
handleRequestSort = (event, property) => {
const orderBy = property;
let order = 'desc';
if (this.state.orderBy === property && this.state.order === 'desc') {
order = 'asc';
}
this.setState({ order, orderBy });
};
handleSelectAllClick = event => {
if (event.target.checked) {
this.setState(state => ({ selected: state.data.map(n => n.id) }));
return;
}
this.setState({ selected: [] });
};
handleClick = (event, id) => {
const { selected } = this.state;
const selectedIndex = selected.indexOf(id);
let newSelected = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, id);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
this.setState({ selected: newSelected });
};
handleChangePage = (event, page) => {
this.setState({ page });
};
handleChangeRowsPerPage = event => {
this.setState({ rowsPerPage: event.target.value });
};
isSelected = id => this.state.selected.indexOf(id) !== -1;
render() {
const { classes } = this.props;
return (
<GridContainer>
<GridItem xs={12} sm={12} md={12}>
{this.checkCurrentButton()}
<Card>
<CardHeader color="primary">
<h4 className={classes.cardTitleWhite}>User List</h4>
<p className={classes.cardCategoryWhite}>
{}
</p>
</CardHeader>
<CardBody>
{this.displayUser()}
</CardBody>
<CardFooter>
</CardFooter>
</Card>
</GridItem>
</GridContainer>
);
}
}
User.propTypes = {
classes: PropTypes.object.isRequired,
};
You need to pass the deleteUser function from your parent component to the child component. You can do this in the props. You're calling the EnhancedTableToolbar inside of the displayUsers method, so in it do the following:
return(
<Paper className={classes.root}>
<EnhancedTableToolbar
numSelected={selected.length}
selectedArray={selected}
deleteUser={this.deleteUser} // you pass the deleteUser of the parent component to the EnhancedTableToolbar as a prop
/>
Then use it inside of the EnhancedTableToolbar
<IconButton aria-label="Delete">
<DeleteIcon onClick={() => { if (window.confirm('Are you sure you wish to delete '+numSelected +' item?')) this.props.deleteUser() } }>
</DeleteIcon>
Your Toolbar component has to receive a property such as onDeleteUser
This way the parent can give the Toolbar a way for it to “call back” when something such as a click happened.
So Toolbar will do something like onClick={this.props.onDeleteUser}
When someone clicks and that event fires, the function is called and the Parent can delete the user.
My design currently stands like this
I am using Material UI's radio button, i want to make each row select able just once. When i add a <RadioButton> component, i am able to select it but however i am not able to toggle between rows
transactionRow(member: Object) {
return (
<tr id='drawLotteryTabel' style={styles.tr} key={member.uuid}>
<td className="col-md-2 col-xs-2">{member.user.fullName}</td>
<td className="col-md-1 col-xs-1">{this.getSubscriptionDropDown(member.subscriptions)}</td>
<td className="col-md-2 col-xs-2">{CommonConstants.INR_SYMBOL + ' ' + Utils.formatNumberLocalised(member.bidDiscountAmount || 0)}</td>
<td className="col-md-2 col-xs-2">{CommonConstants.INR_SYMBOL + ' ' + Utils.formatNumberLocalised(member.bidDiscountPercent || 0)}</td>
<td className="col-md-2 col-xs-2">{CommonConstants.INR_SYMBOL + ' ' + Utils.formatNumberLocalised(member.unpaidAmount || 0)}</td>
<td
className="col-md-2 col-xs-2"
>
<RadioButtonGroup name="shipSpeed" defaultSelected="not_light" key={member.uuid}>
<RadioButton
value="light"
style={styles.radioButton}
/>
</RadioButtonGroup></td>
</tr>
);
}
The Above code results in something like this
What should i do so that i get the entire table row as a single entity to select.
You'll probably, unfortunately, need to manage the state of your radio buttons manually, as a controlled component.
Have the RadioButtonGroup include a call to an onChange function that stores the selected value:
handleChange: (event, value) => {
this.setState({selectedButtonValue: value});
}
And then push that value to every RadioButtonGroup using the valueSelected property as such;
<RadioButtonGroup name="shipSpeed" defaultSelected="not_light" key={member.uuid}
onChange={this.handleChange}
valueSelected={this.state.selectedButtonValue}
>
I assume you are using "Enhanced Table" from material ui.
If that's the case
replace
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, name);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
with
if (selectedIndex === -1) {
newSelected = [name];
}
and replace
The Checkbox component with Radio component.
change this
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{ 'aria-labelledby': labelId }}
/>
</TableCell>
to this
<TableCell padding="checkbox">
<Radio
checked={isItemSelected}
inputProps={{ 'aria-labelledby': labelId }}
/>
</TableCell>
You should be able to toggle between the radio buttons as the button only activates when new row is selected and it replaces the old selected state.
I am attaching the implementation of material ui enhanced table with radio button implementation. I did some tweak here and there. You can take a look.
import React from 'react';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { lighten, makeStyles } from '#material-ui/core/styles';
import Table from '#material-ui/core/Table';
import TableBody from '#material-ui/core/TableBody';
import TableCell from '#material-ui/core/TableCell';
import TableContainer from '#material-ui/core/TableContainer';
import TableHead from '#material-ui/core/TableHead';
import TablePagination from '#material-ui/core/TablePagination';
import TableRow from '#material-ui/core/TableRow';
import TableSortLabel from '#material-ui/core/TableSortLabel';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import Paper from '#material-ui/core/Paper';
import Radio from '#material-ui/core/Radio';
// import IconButton from '#material-ui/core/IconButton';
// import Tooltip from '#material-ui/core/Tooltip';
// import FormControlLabel from '#material-ui/core/FormControlLabel';
// import Switch from '#material-ui/core/Switch';
// import DeleteIcon from '#material-ui/icons/Delete';
// import FilterListIcon from '#material-ui/icons/FilterList';
function createData(name, calories, fat, carbs, protein) {
return { name, calories, fat, carbs, protein };
}
const rows = [
createData('Cupcake', 305, 3.7, 67, 4.3),
createData('Donut', 452, 25.0, 51, 4.9),
createData('Eclair', 262, 16.0, 24, 6.0),
createData('Frozen yoghurt', 159, 6.0, 24, 4.0),
createData('Gingerbread', 356, 16.0, 49, 3.9),
createData('Honeycomb', 408, 3.2, 87, 6.5),
createData('Ice cream sandwich', 237, 9.0, 37, 4.3),
createData('Jelly Bean', 375, 0.0, 94, 0.0),
createData('KitKat', 518, 26.0, 65, 7.0),
createData('Lollipop', 392, 0.2, 98, 0.0),
createData('Marshmallow', 318, 0, 81, 2.0),
createData('Nougat', 360, 19.0, 9, 37.0),
createData('Oreo', 437, 18.0, 63, 4.0),
];
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) return order;
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
const headCells = [
{ id: 'name', numeric: false, disablePadding: true, label: 'Dessert (100g serving)' },
{ id: 'calories', numeric: true, disablePadding: false, label: 'Calories' },
{ id: 'fat', numeric: true, disablePadding: false, label: 'Fat (g)' },
{ id: 'carbs', numeric: true, disablePadding: false, label: 'Carbs (g)' },
{ id: 'protein', numeric: true, disablePadding: false, label: 'Protein (g)' },
];
function EnhancedTableHead(props) {
const { classes, order, orderBy, onRequestSort } = props;
const createSortHandler = (property) => (event) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
</TableCell>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align={headCell.numeric ? 'right' : 'left'}
padding={headCell.disablePadding ? 'none' : 'default'}
sortDirection={orderBy === headCell.id ? order : false}
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<span className={classes.visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</span>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
}
EnhancedTableHead.propTypes = {
classes: PropTypes.object.isRequired,
onRequestSort: PropTypes.func.isRequired,
onSelectAllClick: PropTypes.func.isRequired,
order: PropTypes.oneOf(['asc', 'desc']).isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired,
};
const useToolbarStyles = makeStyles((theme) => ({
root: {
paddingLeft: theme.spacing(2),
paddingRight: theme.spacing(1),
},
highlight:
theme.palette.type === 'light'
? {
color: theme.palette.secondary.main,
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
}
: {
color: theme.palette.text.primary,
backgroundColor: theme.palette.secondary.dark,
},
title: {
flex: '1 1 100%',
},
}));
const EnhancedTableToolbar = (props) => {
const classes = useToolbarStyles();
const { numSelected } = props;
return (
<Toolbar
className={clsx(classes.root, {
[classes.highlight]: numSelected.length > 0,
})}
>
{numSelected.length > 0 ? (
<Typography className={classes.title} color="inherit" variant="subtitle1" component="div">
{numSelected}
</Typography>
) : (
<Typography className={classes.title} variant="h6" id="tableTitle" component="div">
Nutrition
</Typography>
)}
{/* {numSelected.length > 0 ? (
<Tooltip title="Delete">
<IconButton aria-label="delete">
<DeleteIcon />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton aria-label="filter list">
<FilterListIcon />
</IconButton>
</Tooltip>
)} */}
</Toolbar>
);
};
EnhancedTableToolbar.propTypes = {
numSelected: PropTypes.string.isRequired,
};
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
},
paper: {
width: '100%',
marginBottom: theme.spacing(2),
},
table: {
minWidth: 750,
},
visuallyHidden: {
border: 0,
clip: 'rect(0 0 0 0)',
height: 1,
margin: -1,
overflow: 'hidden',
padding: 0,
position: 'absolute',
top: 20,
width: 1,
},
}));
export default function EnhancedTableWithRadio() {
const classes = useStyles();
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState('calories');
const [selected, setSelected] = React.useState('');
const [page, setPage] = React.useState(0);
// const [dense, setDense] = React.useState(false);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const handleRequestSort = (event, property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const handleClick = (event, name) => {
let newSelected = selected;
if (name !== selected) {
newSelected = name;
}
setSelected(newSelected);
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
// const handleChangeDense = (event) => {
// setDense(event.target.checked);
// };
const isSelected = (name) => selected.indexOf(name) !== -1;
const emptyRows = rowsPerPage - Math.min(rowsPerPage, rows.length - page * rowsPerPage);
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar numSelected={selected} />
<TableContainer>
<Table
className={classes.table}
aria-labelledby="tableTitle"
// size={dense ? 'small' : 'medium'}
size = "medium"
aria-label="enhanced table"
>
<EnhancedTableHead
classes={classes}
order={order}
orderBy={orderBy}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{stableSort(rows, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => {
const isItemSelected = isSelected(row.name);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.name}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Radio
checked={isItemSelected}
inputProps={{ 'aria-labelledby': labelId }}
/>
</TableCell>
<TableCell component="th" id={labelId} scope="row" padding="none">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 53 * emptyRows }}>
{/* <TableRow style={{ height: (dense ? 33 : 53) * emptyRows }}> */}
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Paper>
{/* <FormControlLabel
control={<Switch checked={dense} onChange={handleChangeDense} />}
label="Dense padding"
/> */}
</div>
);
}
I had the same problem using a Radio button component inside a cell from a Table, both components from MaterialUi. I resolve like this:
This is my handleChange function to select de value of the Row.id that i use for my app
handleChange(field, event) {
this.setState({[field] : event.target.value});
}
And in my Table component i just use the "Radio" component without a "RadioButtonGoup" component. And use a condition in the "checked" prop of the "Radio" component to show it as checked or not.
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell align="center">Selecciona</TableCell>
<TableCell>Brand</TableCell>
<TableCell align="center">Tarjetahabiente</TableCell>
<TableCell align="center">Terminación</TableCell>
<TableCell align="center">Expira</TableCell>
<TableCell align="center">Eliminar</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map(row => (
<TableRow key={row.id}>
<TableCell component="th" scope="row">
<Radio
value={row.id}
defaultSelected={false}
checked={row.id != this.state.paymentSourceId ? false : true}
onChange={this.handleChange.bind(this, 'paymentSourceId')}
/>
</TableCell>
<TableCell align="center">{row.brand}</TableCell>
<TableCell align="center">{row.name}</TableCell>
<TableCell align="center">{row.last4}</TableCell>
<TableCell align="center">{row.exp_month}/{row.exp_year}</TableCell>
<TableCell align="center">
<IconButton
aria-label="delete"
color="primary"
onClick={()=>this.handleDelete(this.state.paymentSourceId)}>
<DeleteIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>