I have an object containing an array of objects
initialPreconfigTodoState = {
todos: [
{
title: 'title1',
dueDate: new Date(),
create: true,
assignedTo: 'role',
},
{
title: 'title2',
dueDate: new Date(),
create: true,
assignedTo: 'role',
}]
};
I use this array of object to control the state of a Textfield in a table
const [preconfig, setPreconfig] = useState(initialPreconfigTodoState);
{preconfig.todos.map((todo, index) => {
return (
<TableRow
className="h-64 cursor-pointer"
key={Math.random()}
>
<TableCell className="th" component="th" scope="row">
<TextField
id={`titleForm${index}`}
name={`titleForm${index}`}
onChange={(event) => handleTitle(event, index)}
value={todo.title}
type="text"
variant="outlined"
required={todo.create ? true : false}
fullWidth
/>
</TableCell>
when I try to type in the Textfield I don't know what happens but I need to click the Textfield for every character I type, I think is the useState hook that is causing this problem when re-rendering the component and I can't find a solution for this.
this is my handle function on the onChange Callback,
const handleTitle = (event, index) => {
let newArray = [...preconfig.todos];
newArray[index] = {
...newArray[index],
title: event.target.value,
};
setPreconfig({ todos: newArray });
};
here is the full code
const initialPreconfigTodoState = {
todos: [
{
title: "title1",
dueDate: new Date(),
create: true,
assignedTo: "Role"
},
{
title: "title2",
dueDate: new Date(),
create: true,
assignedTo: "Role"
}
]
};
function TodoDialog() {
const [preconfig, setPreconfig] =
useState(initialPreconfigTodoState);
const handleTitle = (event, index) => {
let newArray = [...preconfig.todos];
newArray[index] = {
...newArray[index],
title: event.target.value
};
setPreconfig({ todos: newArray });
};
return (
<div>
<Dialog open={true} maxWidth="xl" scroll="paper">
<DialogContent>
<div>
<Table aria-labelledby="tableTitle">
<TableBody>
<TableRow className="h-64 cursor-pointer" key=.
{Math.random()}>
<TableCell className="th" component="th" scope="row">
Title
</TableCell>
<TableCell className="th" component="th" scope="row">
Due Date
</TableCell>
<TableCell className="th" component="th" scope="row">
Asigned To
</TableCell>
<TableCell className="th" component="th" scope="row">
Create
</TableCell>
</TableRow>
{preconfig.todos.map((todo, index) => {
return (
<TableRow
className="h-64 cursor-pointer"
key={Math.random()}
>
<TableCell className="th" component="th" scope="row">
<TextField
id={`titleForm${index}`}
name={`titleForm${index}`}
onChange={(event) => handleTitle(event, index)}
value={todo.title}
type="text"
variant="outlined"
required={todo.create ? true : false}
fullWidth
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
</DialogContent>
<DialogActions>
<Button color="primary" variant="contained">
Cancel
</Button>
<Button type="submit" color="primary" variant="contained" autoFocus>
Save
</Button>
</DialogActions>
</Dialog>
</div>
);
}
That's because the key={Math.random()} on TableRow.
Change it to key={index} and it should work.
With the Math.random() your keys changes every rendering and react loses its reference.
The issue is caused by thenuseState indeed. Your whole form (I suppose it's a form of some kind) changes the array from which it's mapped in render. No wonder the whole form re-renders.
The obvious solution would be to store the input values (eg in state) and update the original array (via setPreconfig) only on submit.
Related
I am able to print the updated value of dataset but the new added row did not reflecting the updated data from the table component display.
I temporarily put the event on keypress to trigger for adding new row on the table, here the simple code that I playing.
function createData(hardware, cost, status) {
return { hardware, cost, status };
}
const tblData = [];
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
tblData: [],
};
}
render() {
return (
<><><Box
sx={{
width: 500,
maxWidth: '100%',
}}
>
<TextField
fullWidth
label="Scan Here"
id="fullWidth"
onKeyPress={(e) => {
if (e.key === 'Enter') {
//*****--------------------------------------------------
// - Here is the part where I was able to get the updated data but not reflecting on the table display
console.log('Enter key pressed');
tblData.push(createData('blah', 356, 'Yes'));
console.log(tblData);
//
//--------------------------------------------------*****
}
} } />
</Box><TableContainer component={Paper}>
<Table sx={{ minWidth: 650, marginTop: '10px' }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Hardware</TableCell>
<TableCell align="right">Cost</TableCell>
<TableCell align="right">status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{tblData.map((row) => (
<TableRow
key={row.name}
sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
>
<TableCell component="th" scope="row">
{row.hardware}
</TableCell>
<TableCell align="right">{row.cost}</TableCell>
<TableCell align="right">{row.status}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer></>
)
}
}
export default Main;
Any suggestion or comments how to refresh the updated data. TIA
Just pushing element to the state doesn't trigger rerendering.
<TextField
fullWidth
label="Scan Here"
id="fullWidth"
onKeyPress={(e) => {
console.log('Enter key pressed');
this.setState((prev) => {
tblData: prev.tblData.concat([createData('blah', 356, 'Yes')]) // this line is edited.
}))
}}
/>
Hope this would be helpful for you.
It's working now, I just missed the setState to refresh the value.
console.log('Enter key pressed');
tblData.push(createData('blah', 356, 'Yes'));
this.setState({
setState: tblData
});
I have a table which has arrow on each row. When the arrow is clicked, the row is expected to expand and show more data in a tabular form. When the arrow is clicked again, the expanded nested table should collapse. The code is in typescript.
This is what I have so far. I have created the first table and able to display the data.I am not able to find the right way to display the second nested table.
const columns = [
"First Name",
"Last Name",
];
const [openRows, setOpenRows] = useState<Record<string, boolean>>({});
const toggleRow = (dataRow: any) =>
setOpenRows((prevState) => ({
...prevState,
[dataRow.userId]: true,
}));
return (
<Paper elevation={3} variant="outlined">
<TableContainer className={classes.container}>
<Table stickyHeader classes={{ root: classes.tableRoot }}>
<colgroup>
<col width="5%" />
<col width="10%" />
<col width="15%" />
</colgroup>
<TableHead>
<TableRow>
{columns.map((column, idx) => (
<TableCell
key={column}
className={classes.cell}
role="columnheader"
colSpan={idx === 0 ? 2 : 1}
>
<Typography variant="h6">{column}</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{data.map((dataRow) => (
<TableRow
key={dataRow.userId}
title="tableRow"
className={classes.tableRow}
classes={{ hover: classes.hover }}
hover
>
<TableCell classes={{ root: classes.arrowCell }}>
<IconButton
size="small"
onClick={() => toggleRow(dataRow.userId)}
>
{openRows ? <ArrowDropDownIcon /> : <ArrowDropUpIcon />}
</IconButton>
<Collapse
in={openRows[dataRow.userId] ?? false}
timeout="auto"
unmountOnExit
>
<Box margin={1}>
<TableCell>{dataRow.jobDetails.company}</TableCell>
<TableCell>{dataRow.jobDetails.title}</TableCell>
</Box>
</Collapse>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Paper>
);
};
export interface UserData {
userId: string;
firstName: string;
lastName: string;
jobDetails: {
company: string;
title: string;
};
}
interface UserDataProps {
data: UserData[];
}
Thank you so much.
I'm using React and meterial-ui. The two components from material ui I am using are Collapsible Table and a Tree view inside it.
The data structure goes like this:
Projects -> workpackages -> work packages within work packages
So I list my projects in my table, then when you click the collapsible arrow it drops down and displays the work packges within that project in a tree, now I'm trying to figure out how to display sub trees within the tree if a work package has more work packages within it.
My goal is to populate the tree with some hardcoded data, then if it has another child object of data within it, create a sub tree.
I currently have it to where the tree is displaying within the table, but I can't figure out how to display a sub tree.
Columns for my table:
const columns = [
{ field: "project", title: "Project" },
{ field: "projectManager", title: "Project Manager" },
{ field: "startDate", title: "Start Date" },
{ field: "endDate", title: "End Date" },
];
Projects data for the table:
const projects = [
{
title: "Build Backend",
projectManager: "Bruce",
startDate: "12/12/12",
endDate: "12/12/16"
},
{
title: "Build Frontend",
projectManager: "Medhat",
startDate: "11/11/22",
endDate: "12/12/25"
}
]
My Table:
export default function CollapsibleTable() {
return (
<TableContainer component={Paper}>
<Table aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell />
{columns.map((column) => (
<TableCell key={column.field}>
<Typography variant="body1">
<strong>{column.title}</strong>
</Typography>
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{projects.map((row) => (
<Row key={row.title} row={row} />
))}
</TableBody>
</Table>
</TableContainer>
);
}
My work packages data, the first one has a sub work package within it 'package':
const workPackages = [
{
title: "Setup Database",
startDate: "01/12/22",
package: {
title: "Create Crud"
}
},
{
title: "Install express",
startDate: "01/11/12"
}
]
Table rows within my table that have the tree view:
function Row(props) {
const { row } = props;
const [open, setOpen] = React.useState(false);
return (
<React.Fragment>
<TableRow sx={{ '& > *': { borderBottom: 'unset' } }}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
</IconButton>
</TableCell>
<TableCell component="th" scope="row">
{row.title}
</TableCell>
<TableCell >{row.projectManager}</TableCell>
<TableCell >{row.startDate}</TableCell>
<TableCell >{row.endDate}</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<TreeView
aria-label="file system navigator"
defaultCollapseIcon={<ExpandMoreIcon />}
defaultExpandIcon={<ChevronRightIcon />}
sx={{ height: 240, flexGrow: 1, maxWidth: 400, overflowY: 'auto' }}
>
{workPackages.map((tree) => (
<TreeItem label={tree.title}>
</TreeItem>
))}
</TreeView>
</Collapse>
</TableCell>
</TableRow>
</React.Fragment>
);
}
How it looks currently:
Table
How do I check and add a subtree to the tree for that package?
Read material ui docs (Multi selection section). They show how to add a tree in another one (subtree).
I have suddenly run into this problem in my app, the componentDidMount does not work when loading the page and I get typescript error due to the information not being loaded. It was working fine yesterday and the day before and now I have no idea what I did wrong. I have 4 api calls for the method GET, it's only for the GET method. Is there a way to make the component work again ? I have no idea why it behaves this way.
class adminView extends Component {
constructor(props) {
super(props);
this.state = {
Users: [],
Movies: [],
Comments: [],
Ratings: [],
message: null,
};
}
componentDidMount() {
this.reloadObjectiveList().then((r) => console.log("result" + r));
this.reloadUserList().then((r) => {
console.log("result" + r);
});
}
async reloadUserList() {
await UserApiService.fetchUsers().then((res) => {
console.log(res);
this.setState({
Users: res.data.result,
});
});
}
async reloadObjectiveList() {
await UserApiService.fetchUsers().then((res) => {
console.log(res);
this.setState({
Users: res.data.result,
});
});
await MovieApiService.fetchMovies().then((res) => {
this.setState({
Movies: res.data.result,
});
});
await CommentApiService.fetchComments().then((res) => {
console.log(res.data.result);
this.setState({
Comments: res.data.result,
});
});
await RatingApiService.fetchrating().then((res) => {
console.log(res.data.result);
this.setState({
Ratings: res.data.result,
});
});
}
deleteUsersComments(ObjectiveId) {
CommentApiService.deleteComment(ObjectiveId).then((res) => {
this.setState({ message: "Objective deleted successfully." });
this.setState({
Comments: this.state.Comments.filter(
(Objective) => Objective.username !== ObjectiveId
),
});
});
}
deleteUsersRatingss(ObjectiveId) {
RatingApiService.deleterating(ObjectiveId).then((res) => {
this.setState({ message: "Objective deleted successfully." });
this.setState({
Ratings: this.state.Ratings.filter(
(Objective) => Objective.username !== ObjectiveId
),
});
});
}
deleteMovies(ObjectiveId) {
MovieApiService.deleteTourist(ObjectiveId).then((res) => {
this.setState({ message: "Objective deleted successfully." });
this.setState({
Movies: this.state.Movies.filter(
(Objective) => Objective.imdb_id !== ObjectiveId
),
});
});
}
deleteUsers(ObjectiveId) {
UserApiService.deleteTourist(ObjectiveId).then((res) => {
this.setState({ message: "Objective deleted successfully." });
this.setState({
Movies: this.state.Movies.filter(
(Objective) => Objective.username !== ObjectiveId
),
});
});
}
render() {
const { details, loading } = this.props;
return (
<main className={styles.main}>
<Paper className={styles.paper}>
<Typography component="h1" variant="h5">
Hello ADMIN
</Typography>
<hr />
<Typography component="h1" variant="h5">
Users:
</Typography>
<div className="card-list">
{console.log(this.state.Users)}
{this.state.Users.map((item) => (
<CardUsers item={item} key={item.username} />
))}
</div>
<Table>
<TableHead>
<TableRow>
<TableCell>UserFirstName</TableCell>
<TableCell>UserLastName</TableCell>
<TableCell>UserEmail</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.Users.map((row) => (
<TableRow key={row.username}>
<TableCell align="center">{row.firstName}</TableCell>
<TableCell align="center">{row.lastName}</TableCell>
<TableCell align="center">{row.email}</TableCell>
<TableCell
align="center"
onClick={() => this.deleteUsers(row.username)}
>
<DeleteIcon />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<hr />
<Typography component="h1" variant="h5">
MOVIES
</Typography>
<hr />
<div className="card-list">
<Card item={this.state.Movies} />
</div>
<Typography component="h1" variant="h5">
Comments
</Typography>
<hr />
<hr />
<Table>
<TableHead>
<TableRow>
<TableCell>UserId</TableCell>
<TableCell>Comment</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.Comments.map((row) => (
<TableRow key={row.username}>
<TableCell align="center">{row.username}</TableCell>
<TableHead>
<TableCell>MovieID</TableCell>
<TableCell>Comment</TableCell>
</TableHead>
<TableBody>
{row.commentsList.map((row2) => (
<TableRow key={row2.id}>
<TableCell align="center">{row2.movieID}</TableCell>
<TableCell align="center">{row2.comment}</TableCell>
<TableCell
align="center"
onClick={() => this.deleteUsersComments(row2)}
>
<DeleteIcon />
</TableCell>
</TableRow>
))}
</TableBody>
</TableRow>
))}
</TableBody>
</Table>
<Typography component="h1" variant="h5">
Ratings
</Typography>
<hr />
<hr />
<Table>
<TableHead>
<TableRow>
<TableCell>UserId</TableCell>
<TableCell>Ratings</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.Ratings.map((row) => (
<TableRow key={row.username}>
<TableCell align="center">{row.username}</TableCell>
<TableHead>
<TableCell>MovieID</TableCell>
<TableCell>Rating</TableCell>
</TableHead>
<TableBody>
{row.ratingsList.map((row2) => (
<TableRow key={row2.id}>
<TableCell align="center">{row2.movieID}</TableCell>
<TableCell align="center">{row2.rating}</TableCell>
<TableCell
align="center"
onClick={() => this.deleteUsersRatingss(row2)}
>
<DeleteIcon />
</TableCell>
</TableRow>
))}
</TableBody>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
</main>
);
}
}
export default withRouter(withStyles(styles)(adminView));
Currently My table view is like following:
Mui Table The table has a column name 'Action' which has a edit icon button.
Now I want to show (visibility) edit icon to edit each row only when user will hover over table row.
I have tried to override MUITable theme of Material Table but the following code didn't work. can anybody help me?
const getMuiTheme = () => createMuiTheme({
overrides: {
MUIDataTableBodyCell: {
root: {
'&:last-child': {
visibility: 'hidden'
}
}
},
MuiTableRow: {
root: {
'&$hover:hover': {
'& .MUIDataTableBodyCell-root': {
'&:last-child': {
visibility: 'visible'
}
}
}
}
}
}
});
This is an example for that purpose: https://codesandbox.io/s/material-demo-hr3te?file=/demo.js.
Basically I do something:
Add new state to store a boolean variable to determine when to show/hide a component.
Add onMouseEnter and onMouseLeave to TableRow component, for hover effect.
Then set state according to the hover event above.
That's it!
You need to add one action column in your columns array like below:
const columns = [
{
name: 'id',
label: 'id',
options: {
sort: false,
viewColumns: false,
display: false,
filter: false
}
},
{
name: 'transportationBranch',
label: 'Transportation Branch',
options: {
sort: false,
viewColumns: false
}
},
{
name: 'charge',
label: 'Charge',
options: {
filter: false,
sort: false
}
},
{
name: 'tax',
label: 'Tax',
options: {
filter: false,
sort: false
}
},
{
name: '',
label: '',
options: {
filter: false,
sort: false,
viewColumns: false,
customBodyRender: (value, tableMeta, updateValue) => {
return (
<IconButton
id={'Edit-' + tableMeta.rowIndex}
style={{display: 'none'}}
component="button"
variant="body2"
onClick={(event) => {
console.log(event);
alert(tableMeta.rowIndex);
}}
>
<EditIcon />
</IconButton>
);
}
}
}
];
add the the following option in mui datatable options:
setRowProps: (row, dataIndex, rowIndex) => {
return {
onMouseEnter: (e) => handleRowHover(e, row, rowIndex),
onMouseLeave: (e) => handleRowHoverLeave(e, row, rowIndex)
};
},
Write these two event in your component:
function handleRowHover(event, row, rowIndex) {
let control = document.getElementById('Edit-' + rowIndex);
control.style.display = 'block';
}
function handleRowHoverLeave(event, row, rowIndex) {
let control = document.getElementById('Edit-' + rowIndex);
control.style.display = 'none';
}
Note: we used id={'Edit-' + tableMeta.rowIndex} in IconButton so
that we can use getElementById in our two events.
If you can implement this properly then you will see this type ui on your browser:
I modified my original answer according to the mods comments:
I modified ShinaBR2 answer, so that it shows only a text in the current row: https://codesandbox.io/s/material-ui-table-onhover-action-zfloy?file=/demo.js
The idea is to use the row id on mouse enter (or mouse hover) and compare it with the current row, that is hovered to display the element in the row.
export default function SimpleTable() {
const classes = useStyles();
const [showActionId, setShowActionId] = useState(-1);
return (
<TableContainer component={Paper}>
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>Dessert (100g serving)</TableCell>
<TableCell align="right">Calories</TableCell>
<TableCell align="right">Fat (g)</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow
key={row.name}
onMouseEnter={() => {
setShowActionId(row.id); // set id here
}}
onMouseLeave={() => setShowActionId(-1)}
>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">
{row.id === showActionId ? "Show me" : ""} // check the id here and display the message
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
One approach for this is css in js code , we make the icon hidden by default(hiddePin in makeStyles), then change the 'hidden' to 'visible' on hover using "&:hover $clearHidden".
below code was verified . my place is not good work with CodeSandBox(network issue) , so i just show the code here .
const useStyles = makeStyles({
table: {
minWidth: 350,
},
root: {
"&:hover $clearHidden": {
visibility: "visible"
}
},
clearHidden: {},
hiddePin: {
visibility: "hidden"
}
});
return (
<Table className={classes.table} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>name</TableCell>
<TableCell align="center">desc</TableCell>
<TableCell align="center">title2</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.name} hover={true}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="center">{row.desc}</TableCell>
<TableCell align="center" className={classes.root} >
<IconButton size="small" className={clsx(classes.hiddePin, classes.clearHidden)} onClick={(event) => handlePinClick(event,row.stamp)}>
<SvgPinOutLine />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
To only show the actions in the row your hovering over, I added the following useState objects
const [showAction, setShowAction] = React.useState(false);
const [showId, setShowId] = React.useState(0);
and then in the row object
onMouseEnter={() => {
setShowAction(true);
setShowId(row.id);}
}
onMouseLeave={() => setShowAction(false)}
and then in the specific cell that shows the actions:
{row.id == showId && showAction ? (
<IconButton>
<DeleteIcon/>
</IconButton>
) : (
<Box></Box>)}
I hope this helps someone!