How to keep a dynamic table row when it has no value? - html

I have this code :
const HistoricalGrid = ((props) => {
return (
<div className="main-table">
<DataTable rows={props.selectedFile} headers={headers}>
{({ rows, headers, getTableProps, getHeaderProps, getRowProps }) => (
<Table {...getTableProps()}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader {...getHeaderProps({ header })}>
{header.header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>
{props.selectedFile.map((row) => (
<TableRow rows="4" {...getRowProps({ row })}>
<TableCell key={row.id} >{row.name}</TableCell>
<TableCell key={row.id}>{row.type}</TableCell>
<TableCell key={row.id}>{new Date().toString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
)}
</DataTable>
But, I only get the rows data when a button is clicked, how can I keep the rows even if has no value ?

you can check the array length and use a ternary :
{props.selectedFile.length
? props.selectedFile.map((row) => (
...
))
: <TableRow>No result</TableRow>
}

const generateNRows = nb => {
const rows = [];
for($i = 0; $i < $nb; $i++){
rows.push(<tr/>);
}
return rows;
}
const nowDate = new Date();
return (...
{props.selectedFile.map((row) => (
<TableRow {...row} key={row.id} >
<TableCell>{row.name}</TableCell>
<TableCell>{row.type}</TableCell>
<TableCell>{nowDate.toString()}</TableCell>
</TableRow>
))}
{props.selectedFile.length < 4 && (
<>
{generateNRows(4 - props.selectedFile.length)}
</>
)}

Related

Three Rows Table using Material-UI and React

I need to achieve something like this in the picture.
I need to create a table with three rows, where the third row is below the first and second rows, and the width of the third row is the combined width of the first and second rows.
CODESANDBOX: CLICK HERE FOR CODESANDBOX
CODE
const CustomTable = () => {
const handleSubmit = () => {};
return (
<TableContainer component={Paper}>
<Formik
initialValues={[
{
id: 1,
attribute: "",
thirdRow: ""
}
]}
onSubmit={handleSubmit}
>
{({ values }) => (
<Form>
<FieldArray
name="rows"
render={(arrayHelpers) => (
<React.Fragment>
<Box>
<Button
variant="contained"
type="submit"
startIcon={<AddIcon />}
onClick={() =>
arrayHelpers.unshift({
id: Date.now(),
attribute: "",
ruleId: "",
thirdRow: ""
})
}
>
Add New
</Button>
</Box>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Attribute</TableCell>
<TableCell>
<span />
Rule ID
</TableCell>
<TableCell colSpan={2}>Third Row</TableCell>
</TableRow>
</TableHead>
<TableBody>
{values.rows?.map((row, index) => (
<CustomTableRow
key={row.id}
row={row}
index={index}
arrayHelpers={arrayHelpers}
/>
))}
</TableBody>
</Table>
</React.Fragment>
)}
/>
</Form>
)}
</Formik>
</TableContainer>
);
};
export default CustomTable;
You could tweak each "row" to really render 2 rows where the second row's column spans 2 column widths.
demo.js
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow
sx={{
"th": { border: 0 }
}}
>
<TableCell>Attribute</TableCell>
<TableCell>Rule ID</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>Third Row</TableCell>
</TableRow>
</TableHead>
<TableBody>
{values.rows?.map((row, index) => (
<CustomTableRow
key={row.id}
row={row}
index={index}
arrayHelpers={arrayHelpers}
/>
))}
</TableBody>
</Table>
CustomTableRow
const CustomTableRow = ({ row, index }) => {
return (
<>
<TableRow
sx={{
"th, td": { border: 0 }
}}
>
<TableCell component="th" scope="row">
<FastField
name={`rows.${index}.attribute`}
component={TextField}
fullWidth
/>
</TableCell>
<TableCell>
<FastField
name={`rows.${index}.ruleId`}
component={TextField}
fullWidth
/>
</TableCell>
</TableRow>
<TableRow>
<TableCell colSpan={2}>
<FastField
name={`rows.${index}.thirdRow`}
component={TextField}
fullWidth
/>
</TableCell>
</TableRow>
</>
);
};
I prefer to use grid layout. You won't then use Table elements but will then want to give the role attribute for accessibility.
The nice thing about using grid layout is that you get a lot of flexibility so you can then say check for [theme.breakpoints.down("md")] and adjust your gridTemplateAreas accordingly.
So, you would do something like the following to get all the 3 rows stacked one over the other.
[theme.breakpoints.down("md")]: {
gridTemplateColumns: "1fr",
gridTemplateAreas: `"upper1"
"upper2"
"bottom"`,
gap: "0px",
}
The other benefit of using gridArea is that the display is visual so it is easy to see as you are designing your layout
I forked your codesanbox and the link and it is at https://codesandbox.io/s/dry-tdd-vv6z1s?file=/demo.js
The complete code is given below.
import React from "react";
import Table from "#mui/material/Table";
import TableBody from "#mui/material/TableBody";
import TableCell from "#mui/material/TableCell";
import TableContainer from "#mui/material/TableContainer";
import TableHead from "#mui/material/TableHead";
import TableRow from "#mui/material/TableRow";
import Paper from "#mui/material/Paper";
import { Box, Button } from "#mui/material";
import AddIcon from "#mui/icons-material/Add";
import { FieldArray, Form, Formik } from "formik";
import CustomTableRow from "./CustomTableRow";
import { styled } from "#mui/material/styles";
const Wrapper = styled(Box)(({ theme }) => ({
display: "grid",
gridTemplateColumns: "1fr 1fr",
gridTemplateAreas: `"upper1 upper2 "
"bottom bottom"`,
gap: "0px"
}));
const Upper1 = styled(Box)(({ theme }) => ({
gridArea: "upper1"
}));
const Upper2 = styled(Box)(({ theme }) => ({
gridArea: "upper2"
}));
const Bottom = styled(Box)(({ theme }) => ({
gridArea: "bottom"
}));
const CustomTable = () => {
const handleSubmit = () => {};
return (
<TableContainer component={Paper}>
<Formik
initialValues={[
{
id: 1,
attribute: "",
ruleId: "",
thirdRow: ""
}
]}
onSubmit={handleSubmit}
>
{({ values }) => (
<Form>
<FieldArray
name="rows"
render={(arrayHelpers) => (
<React.Fragment>
<Box>
<Button
variant="contained"
type="submit"
startIcon={<AddIcon />}
onClick={() =>
arrayHelpers.unshift({
id: Date.now(),
attribute: "",
ruleId: "",
thirdRow: ""
})
}
>
Add New
</Button>
</Box>
<Box aria-label="simple table">
<Wrapper>
<Upper1 style={{ border: "2px blue solid" }}>
Attribute
</Upper1>
<Upper2 style={{ border: "2px blue solid" }}>
Rule ID
</Upper2>
<Bottom style={{ border: "2px blue solid" }}>
Third Row
</Bottom>
</Wrapper>
<Box>
{values.rows?.map((row, index) => (
<CustomTableRow
key={row.id}
row={row}
index={index}
arrayHelpers={arrayHelpers}
/>
))}
</Box>
</Box>
</React.Fragment>
)}
/>
</Form>
)}
</Formik>
</TableContainer>
);
};
export default CustomTable;

Why my table is not reflecting the sort that I made asap? I need to select multiple times (on my filter by select option) for it to render in tbody

My goal is to have a table that I can sort by location or activationDate using <select>. Now my sorting is now working but I need to select options(location or activationDate) multiple times for it to reflect in the table. What I need to do to make my sorting reflect asap after I select (eg. sort by location)
my sample obect:
[
{
fullName: "honer Baron",
location: "A building",
activationDate: "2022-07-08 09:30:34"
},
{
fullName: "jett valo",
location: "B building",
activationDate: "2022-07-07 10:30:34"
}
]
const AgentSalesModal = ({ obectsToputInTable, show, hide }) => {
const [sortBy , setSortBy] = useState("");
const tableRow = [];
useEffect(() => {
setTableRow();
}, [sortBy ]);
const setTableRow = () => {
if (sortBy == "location") {
return obectsToputInTable
.sort((a, b) => {
console.log(filterBy);
return a.location - b.location;
})
.map((item, key) => (
<tr key={key}>
<td>{item.location}</td>
<td>
{item.fullName}
</td>
<td>{item.activationDate}</td>
</tr>
));
} else {
return obectsToputInTable
.sort((a, b) => {
var dateA = new Date(a.date).getTime();
var dateB = new Date(b.date).getTime();
return dateA > dateB ? 1 : -1;
})
.map((item, key) => (
<tr key={key}>
<td>{item.location}</td>
<td>{item.subscriberId}</td>
<td>
{item.fullName}
</td>
<td>{item.activationDate}</td>
</tr>
));
}
};
const hideModal = () => {
hide();
setSortBy("");
};
return (
<React.Fragment>
<Modal
show={show}
onHide={hideModal}
size="lg"
aria-labelledby="contained-modal-title-vcenter"
centered>
<Modal.Header>
<Modal.Title id="contained-modal-title-vcenter">
Sales Commission
</Modal.Title>
</Modal.Header>
<Modal.Body className="dateModal text-center">
<div className="overflow">
<form>
<select
className="form-select mb-4"
onChange={(e) => setSortBy(e.target.value)}>
<option value="" disabled defaultValue hidden>
Select Value to sort by
</option>
<option value="location">Sort by Location</option>
<option value="activationDate">Sort by Activation Date</option>
</select>
</form>
<table className="table">
<thead>
<tr>
<th>Location</th>
<th>Subscriber Fullname</th>
<th>Activation Date</th>
</tr>
</thead>
<tbody>{setTableRow()}</tbody>
</table>
</div>
</Modal.Body>
<Modal.Footer>
<Button
onClick={() => {
hide();
setSortBy("");
}}>
Close
</Button>
</Modal.Footer>
</Modal>
</React.Fragment>
);
};```
I would use another state to render the result of setTableRow() eg., rows.
const [rows, setRows] = useState([]);
And set the state in the useEffect with the sortBy dependency.
useEffect(() => {
const tableRows = setTableRow();
setRows(tableRows);
}, [sortBy]);
In your render method:
<tbody>{rows}</tbody>
UPDATE
Ideally in this scenario we want to compare using === instead of == but if you introduce antoher filter option a switch will work better, lets try this instead:
const setTableRow = () => {
// Check the value of sortBy here instead
console.log('sortBy', sortBy);
switch(sortBy) {
case "location":
return obectsToputInTable
.sort((a, b) => {
return a.location - b.location;
})
.map((item, key) => (
<tr key={key}>
<td>{item.location}</td>
<td>
{item.fullName}
</td>
<td>{item.activationDate}</td>
</tr>
));
case "activationDate":
return obectsToputInTable
.sort((a, b) => {
var dateA = new Date(a.date).getTime();
var dateB = new Date(b.date).getTime();
return dateA > dateB ? 1 : -1;
})
.map((item, key) => (
<tr key={key}>
<td>{item.location}</td>
<td>{item.subscriberId}</td>
<td>
{item.fullName}
</td>
<td>{item.activationDate}</td>
</tr>
));
default:
return obectsToputInTable
.map((item, key) => (
<tr key={key}>
<td>{item.location}</td>
<td>{item.subscriberId}</td>
<td>
{item.fullName}
</td>
<td>{item.activationDate}</td>
</tr>
));
}
};
Side note: The key in the map(item, key) function simply returns an index value, you may be better off using item.subscriberId instead but I don't see that value in the sample json.

React js material ui core table get data from row on click

I have found a code for a material table that accepts a list as input and applies pagination, sorting and filtering on it. The thing is I need to find a way to extract the data from the row onClick and redirect the page to a new route along with those data. How can I do that?
In the component, I call the table as follows:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
}
and the useTable hook is:
export default function useTable(records, headCells, filterFn) {
const pages = [5, 10, 25]
const [page, setPage] = useState(0)
const [rowsPerPage, setRowsPerPage] = useState(pages[page])
const [order, setOrder] = useState()
const [orderBy, setOrderBy] = useState()
const TblContainer = props => (
<Table className={classes.table}>
{props.children}
</Table>
)
const TblHead = props => {
const handleSortRequest = cellId => {
//Some code
}
return (<TableHead>
<TableRow>
{
headCells.map(headCell => (
<TableCell key={headCell.id}
sortDirection={orderBy === headCell.id ? order : false}>
{headCell.disableSorting ? headCell.label :
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={() => { handleSortRequest(headCell.id) }}>
{headCell.label}
</TableSortLabel>
}
</TableCell>))
}
</TableRow>
</TableHead>)
}
const TblPagination = () => (
<TablePagination
component="div"
page={page}
rowsPerPageOptions={pages}
rowsPerPage={rowsPerPage}
count={records.length}
onChangePage={handleChangePage}
onChangeRowsPerPage={handleChangeRowsPerPage}
id="TablePagination"
/>
)
return {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
}
}
You can simply use an onClick handler to pass the item data through it:
export default function ViewAllUsers() {
const [filterFn, setFilterFn] = useState({ fn: items => { return items; } })
const records = ....//List of records
const {
TblContainer,
TblHead,
TblPagination,
recordsAfterPagingAndSorting
} = useTable(records, headCells, filterFn);
const handleSearch = e => {
let target = e.target;
//Handle search
}
const handleItemClick = item => {
//Redirect to new route from here with the item data
}
return (
<>
<Paper className={classes.pageContent}>
<Toolbar>
<Controls.Input onChange={handleSearch}/>
</Toolbar>
<TblContainer>
<TblHead />
<TableBody>
{
recordsAfterPagingAndSorting().map(item =>
(<TableRow key={item.id} onClick={() => handleItemClick(item)}>
<TableCell>{item.id}</TableCell>
<TableCell>{item.fullName}</TableCell>
</TableRow>)
)
}
</TableBody>
</TblContainer>
<TblPagination/>
</Paper>
</>
)
}

display array data through map in react table

I am trying to display array data in a table for my react app. Every time i reference the data in the table cells in returns the same element for all the columns. I would like to display each of the dates in each column.
My Table:
function SomeComponenet(props) {
return (
<React.Fragment>
{props.attendence.map.forEach((attendence, index) => {
return (
<Paper>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">
{attendence.Attendence[index].date}
</TableCell>
<TableCell align="right">
{attendence.Attendence[index].date}
</TableCell>
<TableCell align="right">
{attendence.Attendence[index].date}
</TableCell>
<TableCell align="right">
{attendence.Attendence[index].date}
</TableCell>
<TableCell align="right">
{attendence.Attendence[index].date}
</TableCell>
</TableRow>
</TableHead>
</Table>
</Paper>
);
})}
</React.Fragment>
);
}
My data:
const fakeData = [
{
Name: "A Person",
Attendence: [
{
date: "2019/12/01",
attendence: 1
},
{
date: "2019/12/02",
attendence: 1
},
{
date: "2019/12/03",
attendence: 1
}
]
}
];
you need another map inside the {props.attendence.map}
link to codesandbox https://codesandbox.io/s/sad-grass-sg5hr
import React, { Fragment } from "react";
import { Table } from "reactstrap";
function Test() {
const attendence = [
{
Name: "A Person",
Attendence: [
{
date: "2019/12/01",
attendence: 1
},
{
date: "2019/12/02",
attendence: 1
},
{
date: "2019/12/03",
attendence: 1
}
]
}
];
return (
<Fragment>
{attendence.map(person => {
return (
<Table>
<thead>
<tr>
<th>Name</th>
{person.Attendence.map(personAttendendance => {
return <th>{personAttendendance.date}</th>;
})}
</tr>
</thead>
<tbody>
<tr>
<td>{person.Name}</td>
{person.Attendence.map(personAttendendance => {
return <td>{personAttendendance.attendence}</td>;
})}
</tr>
</tbody>
</Table>
);
})}
</Fragment>
);
}
export default Test;
that's because all the indexes are the same in one iteration of the topper map. you can't use forEach on a map this way.
you can remove forEach and do another map on attendence.Attendence
something like this:
function SomeComponenet(props) {
return (
<React.Fragment>
{props.attendence.map((attendence, index) =>{
{console.log(attendence.Attendence)}
return(
<Paper >
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
{attendence.Attendence.map( newAttendence => {
return(
<TableCell align="right">
{newAttendence.date}
</TableCell>
)
})}
</TableRow>
</TableHead>
</Table>
</Paper>
);
})}
</ReactFragment>
);
}

Refactoring large View component in React

I have a rather large view component to render a form where the user can add comments, upload images, select between multiple other products and submit it. I keep my state and functions in a separate component and am quite happy with that setup.
The view has grown quite substantially and I am not entirely happy with the structure of the app.
Particularly it annoys me that
I have to pass the same props and classes variables to each component I use
I have a mix of logic in my main view component CreateEntryForm and each of the smaller components
Any tips on how to better structure this code is much appreciated
import React from 'react';
import TextField from 'material-ui/TextField';
import Button from 'material-ui/Button';
import { MenuItem } from 'material-ui/Menu';
import { withStyles } from 'material-ui/styles';
import Chip from 'material-ui/Chip';
import Typography from 'material-ui/Typography';
import { CircularProgress } from 'material-ui/Progress';
import EntryImageView from "./entryImageView";
import {MarkedImage} from "./markedImage";
const styles = theme => ({
button: {
margin: theme.spacing.unit,
},
container: {
display: 'flex',
flexWrap: 'wrap',
},
menu: {
width: 200,
},
chipContainer: {
marginTop: theme.spacing.unit * 2,
display: 'flex',
flexWrap: 'wrap',
},
chip: {
margin: theme.spacing.unit / 2,
},
wrapper: {
marginTop: theme.spacing.unit*2
},
});
const ProductFormGroup = (props) => (
<div>
<TextField
id="selectedProduct"
required
select
label="Select a product"
error={props.selectProductError !== ''}
margin="normal"
fullWidth
value={(props.currentEntry.currentProduct === undefined || props.currentEntry.currentProduct === null) ? "" : props.currentEntry.currentProduct}
onChange={props.handleInputChange('currentProduct')}
SelectProps={{
MenuProps: {
className: props.menu,
},
name: "currentProduct"
}}
helperText={props.selectProductError || ""}
>
{props.job.selectedProducts.map((product, index) => (
<MenuItem key={index} value={product}>
{product.name}
</MenuItem>
))}
</TextField>
<TextField
margin="dense"
id="productQuantity"
label="Product Quantity"
fullWidth
name="productQuantity"
onChange={props.handleInputChange('productQuantity')}
value={props.productQuantity} />
<Button color="primary"
onClick={() => props.addSelectedChip()}
disabled={(props.productQuantity === '' || props.currentEntry.currentProduct === null)}>
add product
</Button>
</div>
);
const SelectedProductsGroup = (props) => (
<div>
<TextField
id="currentUpload"
select
label="Select an image to mark"
margin="normal"
fullWidth
value={props.currentUpload || ''}
onChange={props.handleInputChange('currentUpload')}
SelectProps={{
MenuProps: {
className: props.menu,
},
name: "currentUpload"
}}
>
{props.job.selectedUploads.map((file, index) => (
<MenuItem key={index} value={file}>
{file.name}
</MenuItem>
))}
</TextField>
{props.currentUpload &&
<Button color="primary" onClick={() => props.handleAttachmentDialogOpen(props.job.selectedUploads)}>
Edit marker on image
</Button>}
{props.selectedMarkedImage &&
<MarkedImage markerPosition={props.selectedMarkedImage.position}
attachment={props.selectedMarkedImage.attachment}
imageLoaded={props.markedImageLoaded}
handleImageLoaded={props.handleMarkedImageLoaded}
/>}
</div>
);
const SelectedProductsChipContainer = (props) => (
<div className={props.classes.wrapper}>
<Typography type="subheading" gutterBottom>
Selected Products
</Typography>
<div className={props.classes.chipContainer}>
{props.selectedProducts.map((product, index) => {
return (
<Chip
label={`${product.name} (${product.productQuantity})`}
key={index}
className={props.classes.chip}
onRequestDelete={() => props.handleRequestDeleteChip(product, "product")}
/>
)
})}
</div>
</div>
);
const SelectedImagesView = (props) => (
<div className={props.classes.wrapper}>
<Typography type="subheading" gutterBottom>
Selected images
</Typography>
<input type="file" id="myFile" onChange={props.handleFileUpload} />
{props.uploadLoading
? <CircularProgress/>
: null}
{props.selectedUploads.length > 0 && <EntryImageView selectedUploads={props.selectedUploads}
handleRequestDeleteChip={props.handleRequestDeleteChip} />}
</div>
);
const LocationDescriptionTextField = (props) => (
<TextField
id="locationDescription"
label="Location Description"
multiline
rows="4"
value={props.locationDescription}
onChange={props.handleInputChange('locationDescription')}
margin="normal"
fullWidth
/>
);
const CommentsTextField = (props) => (
<TextField
id="comments"
label="Comments"
multiline
rows="4"
value={props.comments}
onChange={props.handleInputChange('comments')}
margin="normal"
fullWidth
/>
);
export const CreateEntryForm = (props) => {
const { classes } = props;
return (
<div>
{props.job && <SelectedProductsGroup {...props} classes={classes}/>}
{props.selectedProducts.length > 0 && <SelectedProductsChipContainer {...props} classes={classes}/>}
{props.job && <ProductFormGroup {...props}/>}
<SelectedImagesView {...props} classes={classes} />
<LocationDescriptionTextField {...props} />
<CommentsTextField {...props} />
<Button color="primary" onClick={props.handleSubmit}>Create entry</Button>
</div>
)
};
export default withStyles(styles)(CreateEntryForm);