Converting a class into const stateless function - javascript

Hello below I have a class that doesn't use any life-cycle methods or states, I have read documentation about converting such classes into consts. however, I'm not sure how I seem to struggle with the below class:
class ContractsTableHead extends Component {
createSortHandler(property) {
return event => {
this.props.onRequestSort(event, property);
};
}
render() {
const { order, orderBy } = this.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={this.createSortHandler(row.id)}
>
{row.label}
</TableSortLabel>
</Tooltip>
</TableCell>
),
this
)}
</TableRow>
</TableHead>
);
}
}
ContractsTableHead.propTypes = {
onRequestSort: PropTypes.func.isRequired,
order: PropTypes.string.isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired
};
export default ContractsTableHead;

If you make the class component into a function component, you need to make your method createSortHandler into a regular function as well:
const ContractsTableHead = 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>
);
};

Related

Selection Checkbox in React using Hooks

I have a problem selecting a single checkbox or multiple checkbox in a table in React. I'm using Material-UI. Please see my codesandbox here
CLICK HERE
I wanted to achieve something like this in the picture below:
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={false}
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{body?.excluded_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox checked={true} />
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
Seems you are just missing local component state to track the checked status of each checkbox, including the checkbox in the table header.
Here is the implementation for the AddedPersons component since it's more interesting because it has more than one row of data.
Create state to hold the selected persons state. Only add the additional local state, no need to duplicate the passed body prop data (this is anti-pattern anyway) nor add any derived state, i.e. is indeterminate or is all selected (also anti-pattern).
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
Create handlers to toggle the states.
const toggleAllSelected = () => setAllSelected((t) => !t);
const toggleSelected = (id) => () => {
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Use a useEffect hook to toggle all the selected users when the allSelected state is updated.
React.useEffect(() => {
body.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: allSelected
}),
{}
)
);
}, [allSelected, body]);
Compute the selected person count to determine if all users are selected manually or if it is "indeterminate".
const selectedCount = Object.values(selected).filter(Boolean).length;
const isAllSelected = selectedCount === body?.persons?.added_persons?.length;
const isIndeterminate =
selectedCount && selectedCount !== body?.persons?.added_persons?.length;
Attach all the state and callback handlers.
return (
<>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell colSpan={4}>{selectedCount} selected</TableCell>
</TableRow>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={allSelected || isAllSelected} // <-- all selected
onChange={toggleAllSelected} // <-- toggle state
indeterminate={isIndeterminate} // <-- some selected
inputProps={{ "aria-label": "select all desserts" }}
/>
</TableCell>
...
</TableRow>
</TableHead>
<TableBody>
{body?.persons?.added_persons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={selected[row.id] || allSelected} // <-- is selected
onChange={toggleSelected(row.id)} // <-- toggle state
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
Update
Seems there was a bug in my first implementation that disallowed manually deselecting people while the select all checkbox was checked. The fix is to move the logic in the useEffect into the toggleAllSelected handler and use the onChange event to toggle all the correct states. Also to add a check to toggleSelected to deselect "select all" when any person checkboxes have been deselected.
const [allSelected, setAllSelected] = React.useState(false);
const [selected, setSelected] = React.useState({});
const toggleAllSelected = (e) => {
const { checked } = e.target;
setAllSelected(checked);
body?.persons?.added_persons &&
setSelected(
body.persons.added_persons.reduce(
(selected, { id }) => ({
...selected,
[id]: checked
}),
{}
)
);
};
const toggleSelected = (id) => (e) => {
if (!e.target.checked) {
setAllSelected(false);
}
setSelected((selected) => ({
...selected,
[id]: !selected[id]
}));
};
Note: Since both AddedPersons and ExcludedPersons components are basically the same component, i.e. it's a table with same headers and row rendering and selected state, you should refactor these into a single table component and just pass in the row data that is different. This would make your code more DRY.
I have updated your added person table as below,
please note that I am using the component state to update the table state,
const AddedPersons = ({ classes, head, body }) => {
const [addedPersons, setAddedPersons] = useState(
body?.persons?.added_persons.map((person) => ({
...person,
checked: false
}))
);
const [isAllSelected, setAllSelected] = useState(false);
const [isIndeterminate, setIndeterminate] = useState(false);
const onSelectAll = (event) => {
setAllSelected(event.target.checked);
setIndeterminate(false);
setAddedPersons(
addedPersons.map((person) => ({
...person,
checked: event.target.checked
}))
);
};
const onSelect = (event) => {
const index = addedPersons.findIndex(
(person) => person.id === event.target.name
);
// shallow clone
const updatedArray = [...addedPersons];
updatedArray[index].checked = event.target.checked;
setAddedPersons(updatedArray);
// change all select checkbox
if (updatedArray.every((person) => person.checked)) {
setAllSelected(true);
setIndeterminate(false);
} else if (updatedArray.every((person) => !person.checked)) {
setAllSelected(false);
setIndeterminate(false);
} else {
setIndeterminate(true);
}
};
const numSelected = addedPersons.reduce((acc, curr) => {
if (curr.checked) return acc + 1;
return acc;
}, 0);
return (
<>
<Toolbar>
{numSelected > 0 ? (
<Typography color="inherit" variant="subtitle1" component="div">
{numSelected} selected
</Typography>
) : (
<Typography variant="h6" id="tableTitle" component="div">
Added Persons
</Typography>
)}
</Toolbar>
<TableContainer className={classes.tableContainer}>
<Table>
<TableHead className={classes.tableHead}>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
checked={isAllSelected}
inputProps={{ "aria-label": "select all desserts" }}
onChange={onSelectAll}
indeterminate={isIndeterminate}
/>
</TableCell>
{head.map((el) => (
<TableCell key={el} align="left">
{el}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{addedPersons?.map((row, index) => (
<TableRow key={row.id}>
<TableCell padding="checkbox">
<Checkbox
checked={row.checked}
onChange={onSelect}
name={row.id}
/>
</TableCell>
<TableCell align="left">{row.id}</TableCell>
<TableCell align="left">{row.name}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</>
);
};
export default AddedPersons;
Please refer to this for a working example: https://codesandbox.io/s/redux-react-forked-cuy51

Each child in a list should have a unique "key" prop with switch statement

I loop an array and inside that, I loop another array within a switch statement.
<TableBody>
{(rowsPerPage > 0
? filteredItems.slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage
)
: filteredItems
).map((row, index) => (
<TableRow key={index}>
{headers.map(({ value, idx }) => {
switch (value) {
case 'status':
return (
<TableCell key={idx}>
{row.status ? (
<Button
variant='contained'
className={classes.red}
onClick={() =>
changeStatus(row.id, row.status)
}
>
Deactivate
</Button>
) : (
<Button
variant='contained'
className={classes.green}
onClick={() =>
changeStatus(row.id, row.status)
}
>
Activate
</Button>
)}
</TableCell>
);
default:
if (value.split('.').length === 1) {
return (
<TableCell component='th' scope='row' key={idx}>
{row[value]}
</TableCell>
);
} else {
let obj = value.split('.')[0];
let nestObj = value.split('.')[1];
return (
<TableCell component='th' scope='row' key={idx}>
{row[obj] && row[obj][nestObj]}
</TableCell>
);
}
}
})}
</TableRow>
))}
</TableBody>
In both places I have added the key property. Why do I get this error?

TypeError: props.pagination is undefined

I am trying to get page data which is coming from redux store and pass this to local state named pagination. This pagination state is further passed to child component. But the Problem is whenever i try to pass redux state to local state i get error undefined. Here data is defined I can console.log the data but it gets delayed that why i might be getting the error. I don't know how to solve this. I am using react functional component.
newOrder.js
const [pagination, setPagination] = React.useState({});
const DataReceived = (state) =>
state.OrderAndShipping.NewOrderList.newOrder._embedded;
const selectedData = useSelector(DataReceived, shallowEqual);
const NewOrder = selectedData ? selectedData.customerOrderResourceList : null;
const pageState = (state) =>
state.OrderAndShipping.NewOrderList.newOrder.page;
const selectPage = useSelector(pageState);
console.log("page", selectPage);
React.useEffect(() => {
const access_token = localStorage.getItem("access_token");
props.getNewOrderList(access_token, "", ""); <-- redux dispatch function
}, []);
React.useEffect(() => {
setPagination(selectPage); <-- Here i am trying to pass redux state to localstate.
}, []);
const mapStateProps = (state) => {
console.log(state);
return {
newOrder: state.OrderAndShipping.NewOrderList.newOrder
? state.OrderAndShipping.NewOrderList.newOrder._embedded
: null,
};
};
const mapDispatchToProps = {
getNewOrderList, <-- Dispatching function
};
Passing
{TableData && TableData.rows && TableData.rows.length > 0 && (
<Table
_handleCheckbox={_handleCheckbox}
_handlePagination={_handlePagination}
_handleUserCheckBox={_handleUserCheckBox}
data={TableData}
pagination={pagination}
/>
)}
Table.js
const emptyRows =
rowsPerPage -
Math.min(
rowsPerPage,
props.data.rows.length - props.pagination.number * rowsPerPage
);
const { number } = props.pagination;
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar numSelected={selected.length} data={props.data} />
<div className={classes.tableWrapper}>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={dense ? "small" : "medium"}
>
{/*//! Table Head Component */}
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={props.data.rows.length}
data={props.data}
/>
{/*//! Table Body Component */}
<TableBody>
{stableSort(props.data.rows, getSorting(order, orderBy))
.slice(number * rowsPerPage, number * 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, row.userId)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={props.data.rows.name}
selected={isItemSelected}
>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
{/**
* ===============================================
* PAGINATION
* =============================================
*/}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={props.data.rows.length}
rowsPerPage={rowsPerPage}
page={props.pagination.number}
backIconButtonProps={{
"aria-label": "Previous Page",
}}
nextIconButtonProps={{
"aria-label": "Next Page",
}}
onChangePage={props._handlePagination}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Paper>
console.log pagination
console.log("page", selectPage);
Table.js
function EnhancedTable(props) {
const [rowsPerPage, setRowsPerPage] = React.useState(10);
//! Select All Checkbox
function handleSelectAllClick(event) {
if (event.target.checked) {
const newSelecteds = props.data.rows.map((n) => n.name);
setSelected(newSelecteds);
return;
}
setSelected([]);
}
//! Handle CheckBox here
function handleClick(event, name, userId) {
const selectedIndex = selected.indexOf(name);
let newSelected = [];
const selectedIdIndex = SelectedId.indexOf(userId);
let newSelectedIndex = [];
console.log(userId);
let userid = [];
userid = userId;
console.log(selectedIndex);
props._handleCheckbox(selectedIdIndex, userid, SelectedId);
function handleChangeDense(event) {
setDense(event.target.checked);
}
const isSelected = (name) => selected.indexOf(name) !== -1;
const emptyRows =
rowsPerPage -
Math.min(
rowsPerPage,
props.data.rows.length - props.pagination.number * rowsPerPage
);
const { number } = props.pagination;
return (
<div className={classes.root}>
<Paper className={classes.paper}>
<EnhancedTableToolbar numSelected={selected.length} data={props.data} />
<div className={classes.tableWrapper}>
<Table
className={classes.table}
aria-labelledby="tableTitle"
size={dense ? "small" : "medium"}
>
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={props.data.rows.length}
data={props.data}
/>
{/*//! Table Body Component */}
<TableBody>
{stableSort(props.data.rows, getSorting(order, orderBy))
.slice(number * rowsPerPage, number * 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, row.userId)
}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={props.data.rows.name}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isItemSelected}
inputProps={{ "aria-labelledby": labelId }}
/>
</TableCell>
{rowData(row)}
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
{/**
* ===============================================
* PAGINATION
* =============================================
*/}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={props.data.rows.length}
rowsPerPage={rowsPerPage}
page={props.pagination.number}
backIconButtonProps={{
"aria-label": "Previous Page",
}}
nextIconButtonProps={{
"aria-label": "Next Page",
}}
onChangePage={() => props.handlePagination()}
onChangeRowsPerPage={handleChangeRowsPerPage}
/>
</Paper>
</div>
);
}
const mapStateToProps = (state) => {
return {
checkbox: state.AllUsers.Admin.checkBox,
};
};
export default connect(mapStateToProps, {})(EnhancedTable);
Issue :
As per your console log you are getting selectPage undefined initially, and you also setting up the value only on mount
React.useEffect(() => {
setPagination(selectPage); <-- Here i am trying to pass redux state to localstate.
}, []); // <--- this will executed only on mount
Solution :
I think you should listen for the changes in selectPage and only update If it's available
React.useEffect(() => {
if(selectPage) { // <--- check if available
setPagination(selectPage);
}
}, [selectPage]); // <--- will run useEffect on everychange of `selectPage`

No horizontal scrolling on resized material-ui table

I am struggling to get this table to behave properly. I have set overflow-x to auto for the table. The table is very similar to the material-ui table from here: https://material-ui.com/demos/tables/
The table will shrink to a certain point and will then simply overflow the edge of the page.
gif of the behavior
Class EnhancedTableHead extends Component {
createSortHandler = property => event => {
this.props.onRequestSort(event, property);
};
render() {
const {
onSelectAllClick,
order,
orderBy,
numSelected,
rowCount
} = this.props;
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
indeterminate={numSelected > 0 && numSelected < rowCount}
checked={numSelected === rowCount}
onChange={onSelectAllClick}
/>
</TableCell>
{columnData.map(column => {
return (
<TableCell
key={column.id}
numeric={column.numeric}
padding={column.disablePadding ? 'none' : 'default'}
sortDirection={orderBy === column.id ? order : false}
>
<Tooltip
title="Sort"
placement={column.numeric ? 'bottom-end' : 'bottom-start'}
enterDelay={300}
>
<TableSortLabel
active={orderBy === column.id}
direction={order}
onClick={this.createSortHandler(column.id)}
>
{column.label}
</TableSortLabel>
</Tooltip>
</TableCell>
);
}, this)}
</TableRow>
</TableHead>
);
}
}
EnhancedTableHead.propTypes = {
numSelected: PropTypes.number.isRequired,
onRequestSort: PropTypes.func.isRequired,
onSelectAllClick: PropTypes.func.isRequired,
order: PropTypes.string.isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired
};
const styles = theme => ({
root: {
width: '100%',
marginTop: theme.spacing.unit * 3
},
table: {
minWidth: 700
},
tableWrapper: {
overflowX: 'auto'
}
});
class Companies extends Component {
constructor(props, context) {
super(props, context);
this.state = {
order: 'asc',
orderBy: 'info',
selected: [],
data:
this.props.accounts.length > 0
? this.props.accounts.sort((a, b) => (a.name < b.name ? -1 : 1))
: [],
page: 0,
rowsPerPage: 3
};
}
render() {
const { classes } = this.props;
const { data, order, orderBy, selected, rowsPerPage, page } = this.state;
const emptyRows =
rowsPerPage - Math.min(rowsPerPage, data.length - page * rowsPerPage);
return (
<Paper className={classes.root}>
<EnhancedTableToolbar numSelected={selected.length} />
<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>
{data
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map(n => {
const isSelected = this.isSelected(n.ref);
return (
<TableRow
hover
onClick={event => this.handleRowClick(event, n.ref)}
role="checkbox"
aria-checked={isSelected}
tabIndex={-1}
key={n.ref}
selected={isSelected}
>
<TableCell padding="checkbox">
<Checkbox
checked={isSelected}
onChange={event => this.handleClick(event, n.ref)}
/>
</TableCell>
<TableCell>
<Avatar
alt={n.name}
src={`//logo.clearbit.com/${n.clearBit}`}
onError={e => {
e.target.src =
'https://doxhze3l6s7v9.cloudfront.net/app/static/img/company-default-img.png';
}}
/>
</TableCell>
<TableCell>{n.name}</TableCell>
<TableCell>{n.owner}</TableCell>
<TableCell numeric>{n.dateCreated}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow style={{ height: 49 * emptyRows }}>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</div>
<TablePagination
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>
);
}
}
Companies.propTypes = {
classes: PropTypes.object.isRequired
};
Any insight into why the table behaves the way it does would be greatly appreciated. Thank you!

Avoid duplicated codea and extract this functionality

How better to avoid duplicate code here. I try to render albeRow two times with diff props but I have the duplicate code for TableCell Rendering.
{data.map((item, index) =>
selectable && !!selected
? <TableRow
hover
onClick={() => {
onSelect(selected.includes(index)
? selected.filter(x => x != index)
: [...selected, index])}}
role="checkbox"
aria-checked={selected.includes(index)}
tabIndex="-1"
key={index}
selected={selected.includes(index)}
>
<TableCell checkbox>
<Checkbox checked={selected.includes(index)}/>
</TableCell>
{columns.map(({dataKey, cellRenderer, numeric}, index) =>
<TableCell key={index} numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, dataKey})}
</TableCell>)}
</TableRow>
: <TableRow hover key={index}>
{columns.map(({dataKey, cellRenderer, numeric}, index) =>
<TableCell key={index} numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({item, dataKey})}
</TableCell>)}
</TableRow>
)}
As far as I understood, selectable && !!selected is the main condition that handles the option to select rows or not. In that case, I'd use that on my favor and render the component as:
import React from 'react'
import { TableRow, TableCell, Checkbox } from 'anything'
export default function YourCompoment({
columns,
data,
selectable,
selected,
onSelect,
}) {
const canSelect = selectable && !!selected
return (
<div>
{data.map((item, index) =>
<TableRow
hover
onClick={canSelect && () => {
onSelect(
selected.includes(index)
? selected.filter(x => x != index)
: [...selected, index]
)
}}
role={canSelect ? 'checkbox' : 'anyOtherValue'}
aria-checked={canSelect && selected.includes(index)}
tabIndex="-1"
key={index}
selected={canSelect && selected.includes(index)}
>
{canSelect &&
<TableCell checkbox>
<Checkbox checked={selected.includes(index)} />
</TableCell>}
{columns.map(({ dataKey, cellRenderer, numeric }, columnIndex) =>
<TableCell key={columnIndex} numeric={numeric}>
{(cellRenderer || defaultCellRenderer)({ item, dataKey })}
</TableCell>
)}
</TableRow>
)}
</div>
)
}

Categories

Resources