I'm certain there has to be something painfully simple and obvious that I'm just not seeing here... The following component pulls data through RTK Query and then maps out a table. The table is displaying correctly in terms of structure, and the values make it to the child Row component (the table expands to show additional fields, and I followed the Mui example for a collapsible table: https://mui.com/material-ui/react-table/#collapsible-table) as logged in the console. But the actual render of the data is showing undefined. I've used this approach so many times without issue, and I just cannot find anything off, but again, I'm sure it's super obvious and its just my being lost in the weeds. Any help would be greatly appreciated.
function Row(data) {
console.log("data ", data) //data successfully makes it here...
const visitId = data.id;
const [open, setOpen] = useState(false);
const [deleteVisit] = useDeleteVisitMutation();
const formatDate = (visitStart) => {
const date = new Date(visitStart);
let options = {
year: "numeric",
month: "numeric",
day: "numeric",
};
return date.toLocaleDateString("en-US", options);
};
return (
<>
<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">
{data?.user?.fullName} {console.log("user in data", data?.user?.fullName)} //undefined here (and all data values)
</TableCell>
<TableCell align="center">{formatDate(data?.visitStart)}</TableCell>
<TableCell align="center">
<span display='inline-flex'>
<DirectEditVisit visitId={visitId} visit={data} />
<IconButton onClick={() => deleteVisit(visitId)}>
<DeleteIcon />
</IconButton>
</span>
</TableCell>
</TableRow>
<TableRow>
<TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}>
<Collapse in={open} timeout="auto" unmountOnExit>
<Box sx={{ margin: 1 }}>
<Typography variant="h6" gutterBottom component="div">
Progress Notes
</Typography>
<Table size="small" aria-label="notes">
<TableHead>
<TableRow>
<TableCell>Goal</TableCell>
<TableCell>Notes</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data?.goals?.map((goal) => (
<TableRow key={goal._id}>
<TableCell component="th" scope="row">
{goal.title}
</TableCell>
<TableCell>{goal.note}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</Collapse>
</TableCell>
</TableRow>
</>
);
}
const ProgNotesTab = () => {
const { clientId } = useParams();
const { data, isLoading, isSuccess } = useGetVisitsByClientIdQuery(clientId);
let content;
if (isLoading) {
content = <CircularProgress />;
} else if (isSuccess) {
content = (
<div>
<Box sx={{ display: "flex", height: "100%", width: "100%" }}>
<TableContainer component={Paper}>
<Table aria-label="Progress Notes">
<TableHead>
<TableRow>
<TableCell />
<TableCell>Staff</TableCell>
<TableCell>Date</TableCell>
<TableCell>Edit/Delete</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data && data.map((i) => (
<Row key={i._id} data={i} />
))}
</TableBody>
</Table>
</TableContainer>
</Box>
</div>
);
}
return (
<div>
<div>{content}</div>
</div>
);
};
export default ProgNotesTab;
The data in the console:
data: Object { client: "6205a8313fe12d6b4ec354c4", location: "Los Angeles Court", visitStart: "2022-04-13T18:00:53.000Z", … }
client: "6205a8313fe12d6b4ec354c4"
createdAt: "2022-04-13T18:23:15.712Z"
goals: Array(3) [ {…}, {…}, {…} ]
0: Object { title: "Cook", note: "Cook stuff", marked: true, _id: "631f6a7de4c79fe85dc94c3a" }
1: Object { title: "Budget", note: "Budget finances", marked: true, … }
2: Object { title: "Clean", note: "Clean stuff", marked: true, _id: "631f6a7de4c79fe85dc94c3c" }
length: 3
<prototype>: Array []
id: "62571513ec41c091181dd828"
location: "Los Angeles Court"
totalHours: 1
updatedAt: "2022-09-12T17:21:01.917Z"
user: Object { _id: "62194175bcd77d7f4bfa97ea", fullName: "Danny Trejo" }
visitEnd: "2022-04-13T19:00:00.000Z"
visitStart: "2022-04-13T18:00:53.000Z"
Since you pass prop name as data in line <Row key={i._id} data={i} />, you need to replace this line:
function Row(data) {
with:
function Row({ data }) {
You can take a look at this sandbox for a live working example of your case.
Related
I'm trying to fetch data from api and show it with React.
However I could see that errors and I'm difficult with parsing json response from api.
I think that it will be solved if I make response as array.
I don't know how to make it.
page.js
I try to fetch data from API by Native hook and useEffect
I checked API by Postman. So it is working well.
function Customers(props) {
const [data, setData] = useState({});
const [error, setError] = useState(null);
useEffect(() => {
fetch("http://localhost:8080/contacts")
.then((response) => {
if (response.status !== 200) {
setError("Invalid response: ", response.status);
} else {
setError(null);
}
return response.json();
})
.then((json) => {
setData(json);
});
});
if (error !== null) {
return <div>Error: {error.message}</div>
} else {
return (
<DashboardLayout>
<>
<Head>
<title>
Data
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth={false}>
<Box sx={{ mt: 3 }}>
<CustomerListResults customers={data} />
</Box>
</Container>
</Box>
</>
</DashboardLayout>
);
}
}
export default Customers;
list_component.js
I made a table. I would like to show API data this table.
I try to use slice and map to parsing data. but it is not working.
export const CustomerListResults = ({ customers, ...rest }) => {
const [limit, setLimit] = useState(25);
const [page, setPage] = useState(0);
const handlePageChange = (event, newPage) => {
setPage(newPage);
};
return (
<Card {...rest}>
<PerfectScrollbar>
<Box sx={{ minWidth: 1050 }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>
Date
</TableCell>
<TableCell>
Name
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{customers.slice(0,limit).map((customer) => (
<TableRow
hover
key={customers.id}
>
<TableCell>
<Box
sx={{
alignItems: 'center',
display: 'flex'
}}
>
<Typography
color="textPrimary"
variant="body2"
>
{customers.created_date}
</Typography>
</Box>
</TableCell>
<TableCell>
{customers.user_idx}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Box>
</PerfectScrollbar>
<TablePagination
component="div"
count={customers.length}
onPageChange={handlePageChange}
page={page}
rowsPerPage={limit}
/>
</Card>
);
};
CustomerListResults.propTypes = {
customers: PropTypes.array.isRequired
};
That's because data on initial load is equal to an empty object, and object doesn't have a slice method as it's a method for an array.
One solution is set an inital value for data to an empty array.
const [data, setData] = useState([]);
I want to update the text of a button when i click on that button. I am using setState for that but i am unable to use it as it gives me the following error:
TypeError: Cannot read properties of undefined (reading 'state')
Here is my code:
import Head from 'next/head';
import { Box, Container, Grid, Pagination } from '#mui/material';
import { products } from '../__mocks__/products';
import { ProductListToolbar } from '../components/product/product-list-toolbar';
import { ProductCard } from '../components/product/product-card';
import { DashboardLayout } from '../components/dashboard-layout';
import { CustomerListResults } from '../components/trip/trip-list-results';
import { customers } from '../__mocks__/customers';
import { trips } from '../__mocks__/trips';
import { TripListResults } from '../components/customer/customer-list-results';
import {
Avatar,
Card,
Checkbox,
Table,
TableBody,
TableCell,
TableHead,
TablePagination,
TableRow,
Typography,
Button
} from '#mui/material';
import NextLink from 'next/link';
const TripRequests = () => {
const accept =()=> {
this.setState({accept: 'Payment Pending'})
console.log("fsfdsa");
};
this.state = {
accept: "Accept"
};
return (
<>
<Head>
<title>
Trip Requests
</title>
</Head>
<Box
component="main"
sx={{
flexGrow: 1,
py: 8
}}
>
<Container maxWidth={false}>
<Box sx={{ mt: 3 }}>
{/* <CustomerListResults customers={trips} /> */}
<h2>Trip Requests (2)</h2>
</Box>
<Box sx={{ minWidth: 1050, mt: 3 }}>
<Table>
<TableHead>
<TableRow>
<TableCell padding="checkbox">
{/* <Checkbox
// checked={selectedCustomerIds.length === customers.length}
color="primary"
// indeterminate={
// selectedCustomerIds.length > 0
// && selectedCustomerIds.length < customers.length
// }
// onChange={handleSelectAll}
/> */}
</TableCell>
{/* <TableCell>
Trip Id
</TableCell> */}
<TableCell>
Customer
</TableCell>
<TableCell>
Departure
</TableCell>
<TableCell>
Destination
</TableCell>
<TableCell>
Truck / Driver
</TableCell>
<TableCell>
Action
</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow
hover
// key={customer.id}
// selected={selectedCustomerIds.indexOf(customer.id) !== -1}
>
<TableCell padding="checkbox">
{/* <Checkbox
checked={selectedCustomerIds.indexOf(customer.id) !== -1}
onChange={(event) => handleSelectOne(event, customer.id)}
value="true"
/> */}
</TableCell>
<TableCell>
Rohan Joshi
</TableCell>
<TableCell>
<Box
sx={{
alignItems: 'center',
display: 'flex'
}}
>
{/* <Avatar
src={customer.avatarUrl}
sx={{ mr: 2 }}
>
{getInitials(customer.name)}
</Avatar> */}
<Typography
color="textPrimary"
variant="body1"
>
A-50, Sec 67, Noida
</Typography>
</Box>
</TableCell>
<TableCell>
HUDA City Center
</TableCell>
<TableCell>
fds
</TableCell>
<TableCell>
<Button onClick={accept}>{state.accept}</Button>
<Button>Decline</Button>
</TableCell>
{/* <TableCell>
{format(customer.createdAt, 'dd/MM/yyyy')}
</TableCell> */}
</TableRow>
</TableBody>
</Table>
</Box>
</Container>
</Box>
</>
);
TripRequests.getLayout = (page) => (
<DashboardLayout>
{page}
</DashboardLayout>
)};
export default TripRequests;
Looks like you are mixing class-based components with function components. In your case this refers to the module-level this which is undefined. Function components don't make use of this.
To use state in function components you need to use the useState hook:
const TripRequests = () => {
const [acceptState, setAcceptState] = useState('Accept')
const accept = () => setAcceptState('Payment Pending')
// ...
}
Working Code :https://codesandbox.io/s/broken-https-2i454?file=/src/App.js
I'm using Material UI in my reactjs project and I'm trying the update the value entered inside a textfield of a table using onChange function for the textfield, currently the value is present inside an array of array,I want to update the componentQuantity , I'm aware of passing the event to get the event.target.value , but what is the correct way to update the specific value of the textfield present inside a array of array. Please someone help me out here.
class TabData extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
data: [
{
BomComponentCode: "345543",
BomComponentName: "COMP1",
BomComponentRefUOM: "gm",
rowId: 0,
consumptionBatchNumbers: [
{
componentBatchNumber: "20",
componentQuantity: 2
},
{
componentBatchNumber: "21",
componentQuantity: 3
}
]
},
//2nd cloumn
{
BomComponentCode: "5543",
BomComponentName: "COMP2",
BomComponentRefUOM: "KG",
rowId: 1,
consumptionBatchNumbers: [
{
componentBatchNumber: "22",
componentQuantity: 4
},
{
componentBatchNumber: "23",
componentQuantity: 5
}
]
}
],
renderState: false
};
}
handleUpdate = (index, Code) => {
this.setState({
renderState: Code
});
};
handleChange = (e) => {
console.log(e.target.value)
};
render() {
const { classes } = this.props;
const { data, renderState } = this.state;
return (
<div className={classes.list}>
<React.Fragment>
<Grid
item
xs={12}
sm={12}
md={12}
className={classes.grid}
style={{ paddingLeft: "0" }}
>
<p variant="h6" className={classes.font}>
Table Details
</p>
<span className={classes.borders}></span>
</Grid>
<div>
<TableContainer component={Paper} className={classes.paper}>
<Table className={classes.table} aria-label="collapsible table">
<TableHead>
<TableRow>
<TableCell> Number</TableCell>
<TableCell> Text</TableCell>
<TableCell>UOM</TableCell>
<TableCell> Batch </TableCell>
<TableCell> Quantity</TableCell>
<TableCell>Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
<React.Fragment>
{data.map((item, i) => (
<React.Fragment>
<TableRow key={i}>
<TableCell scope="row" align="left">
{item.BomComponentCode}
</TableCell>
<TableCell align="left">
{item.BomComponentName}
</TableCell>
<TableCell align="left">
{item.BomComponentRefUOM}
</TableCell>
<TableCell>
{item.consumptionBatchNumbers.map((row, i) => (
<div key={i}>
<TableCell align="left">
{row.componentBatchNumber}
</TableCell>
</div>
))}
</TableCell>
{renderState === item.BomComponentCode ? (
<TableCell align="left">
{item.consumptionBatchNumbers.map((row, indi) => (
<div key={indi}>
<input
// value={row.componentQuantity}
onChange={(e) =>
this.handleChange(e)
}
defaultValue={row.componentQuantity}
/>
</div>
))}
</TableCell>
) : (
<TableCell align="left">
{item.consumptionBatchNumbers.map((row, indi) => (
<div key={indi}>
<TableCell align="left">
{row.componentQuantity}
</TableCell>
</div>
))}
</TableCell>
)}
<TableCell>
<button
onClick={() =>
this.handleUpdate(i, item.BomComponentCode)
}
>
Update
</button>
</TableCell>
</TableRow>
</React.Fragment>
))}
</React.Fragment>
</TableBody>
</Table>
</TableContainer>
</div>
<Grid container justify="center">
<Grid
item
xs={4}
sm={4}
md={2}
style={{
textAlign: "center",
padding: "1rem"
}}
>
<button >Close</button>
</Grid>
</Grid>
</React.Fragment>
</div>
);
}
}
export default (TabData);
To modify the value in the deeply nested array you need indexes . Once you have the indexes its easier to change the values .
Change the handleChange method to accept the row and deeply nested row's indexes.
onChange={(e) => this.handleChange(e, i, indi)}
Once you have the indexes we can deep clone the original state to create a new copy of state and mutate it directly .
handleChange = (e, rowIndex, consumptionBatchIndex) => {
// deep clone the data
const clonedData = JSON.parse(JSON.stringify(this.state.data));
clonedData[rowIndex].consumptionBatchNumbers[
consumptionBatchIndex
].componentQuantity = e.target.value;
this.setState({
data: clonedData
});
};
Now we can read the value in the input as
<input
value={row.componentQuantity}
onChange={(e) => this.handleChange(e, i, indi)}
/>
I have added a react mat ui table to my app and added switches into one of the columns but for some reason they all toggle together rather than independently.
How can I change this?
<TableBody>
{operators.map((row) => (
<TableRow key={row.key}>
<TableCell component="th" scope="row">
{row.operator}
</TableCell>
<TableCell>
<Typography component="div">
<Grid
component="label"
container
alignItems="center"
// justify="flex-end"
spacing={1}
>
<Grid item>Off</Grid>
<Grid item>
<AntSwitch
checked={checkboxState.checkedC}
onChange={handleChange}
name="checkedC"
/>
</Grid>
<Grid item>On</Grid>
</Grid>
</Typography>
</TableCell>
<TableCell align="right">{'<<Placement value>>'}</TableCell>
</TableRow>
))}
</TableBody>
I just created a demo in a sandbox to show you but the strange thing is that it works correctly there but not in my app.
The full code is in my Gist: https://gist.github.com/SerdarMustafa1/b3214b01885980e433405987f8822fe7
and demo: https://stackblitz.com/edit/5dm8lk?file=demo.js.
Any ideas what's gone wrong?
You can use array of states to keep the state of each raw:
import React, {useState} from "react";
import { withStyles } from "#material-ui/core/styles";
import { purple } from "#material-ui/core/colors";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Switch from "#material-ui/core/Switch";
import Grid from "#material-ui/core/Grid";
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 Typography from '#material-ui/core/Typography';
import TableHead from "#material-ui/core/TableHead";
import TableRow from "#material-ui/core/TableRow";
import Paper from "#material-ui/core/Paper";
import { makeStyles, withStyles } from "#material-ui/core/styles";
const AntSwitch = withStyles(theme => ({
root: {
width: 28,
height: 16,
padding: 0,
display: "flex"
},
switchBase: {
padding: 2,
color: theme.palette.grey[500],
"&$checked": {
transform: "translateX(12px)",
color: theme.palette.common.white,
"& + $track": {
opacity: 1,
backgroundColor: theme.palette.primary.main,
borderColor: theme.palette.primary.main
}
}
},
thumb: {
width: 12,
height: 12,
boxShadow: "none"
},
track: {
border: `1px solid ${theme.palette.grey[500]}`,
borderRadius: 16 / 2,
opacity: 1,
backgroundColor: theme.palette.common.white
},
checked: {}
}))(Switch);
const tableStyles = makeStyles({
table: {
minWidth: 150
}
});
export default function CustomizedSwitches() {
const [gridData, setGridData] = useState([
{ key: 6, operator: "OyPohjolanLiikenne Ab", checked: false },
{ key: 12, operator: "Helsingin Bussiliikenne Oy", checked: true },
{ key: 17, operator: "Tammelundin Liikenne Oy", checked: false },
{ key: 18, operator: "Pohjolan Kaupunkiliikenne Oy", checked: true },
{ key: 20, operator: "Bus Travel Åbergin Linja Oy", checked: false },
{ key: 21, operator: "Bus Travel Oy Reissu Ruoti", checked: true }
]);
const handleChange = (event, index) => {
gridData[index].checked = event.target.checked;
setGridData([...gridData]);
};
const tableClasses = tableStyles();
return (
<TableContainer component={Paper}>
<Table
stickyHeader
className={tableClasses.table}
size="small"
aria-label="a dense table"
>
<TableHead>
<TableRow>
<TableCell>Operator</TableCell>
{/* <TableCell align="right">Listed</TableCell> */}
<TableCell>Visible</TableCell>
<TableCell align="right">Total Placements</TableCell>
</TableRow>
</TableHead>
<TableBody>
{gridData.map( (row, index) => (
<TableRow key={row.key}>
<TableCell component="th" scope="row">
{row.operator}
</TableCell>
<TableCell>
<Typography component="div">
<Grid
component="label"
container
alignItems="center"
// justify="flex-end"
spacing={1}
>
<Grid item>Off</Grid>
<Grid item>
<AntSwitch
checked={row.checked}
onChange={(event) => handleChange(event, index)}
name="checkedC"
/>
</Grid>
<Grid item>On</Grid>
</Grid>
</Typography>
</TableCell>
<TableCell align="right">{"<<Placement value>>"}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
Combining Arthur Ruben's answer and Drew Reese's comment.
Here's how I assigned props to a switch state array to be used by the switch and update fields inside the grid depending on the state of that switch.
Each row inside table needs its own state (and each column, as well, if you want to change columns within rows). You can monitor state of different switches as well as change fileds inside the row this way.
export default function TableComponent(props) {
const { rows } = props;
// generate array from incoming props,
// to be assigned to the state of this components
let stateInitArray = rows.map((row, index) => ({
id: index,
checked: false,
transactionType: "Session",
}));
const [gridState, setGridState] = React.useState(stateInitArray);
const handleSwitchChange = (event, index) => {
const { checked } = event.target;
let transactionType = checked ? "Payment" : "Session";
setGridState((prevState) =>
prevState.map((row, i) =>
// iterate over the state and update value of row where the switch was pressed
i === index ? { ...row, checked, transactionType } : row
)
);
};
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell align="left">isPayment?</TableCell>
<TableCell>Date</TableCell>
<TableCell align="left">Cost</TableCell>
<TableCell align="left">Transaction Type</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Column Gap</TableCell>
<TableCell align="left">Message</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row, index) => (
<TableRow
key={row.name}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell>
<Switch
key={gridState[index].id}
checked={gridState[index].checked}
onChange={(event) => {
handleSwitchChange(event, index);
}}
inputProps={{ "aria-label": "controlled" }}
/>
</TableCell>
<TableCell component="th" scope="row">
{row.date}
</TableCell>
<TableCell align="left">
{gridState[index].transactionType === "Payment" ? "" : row.cost}
</TableCell>
<TableCell align="left">
{gridState[index].transactionType}
</TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left"></TableCell>
<TableCell align="left">{row.message}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
}
I am trying to create a material UI table where each cell is a Formik input. Unfortunately, Material UI throws errors when a Formik Object is a child of a TableBody tag or a TableItem tag.
For example:
Table of Inputs
This is what the example has, without Formik:
{rows.map(row => (
<TableRow key={row.id}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right"></TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">{row.protein}</TableCell>
</TableRow>
))}
This is what I have and it's not working:
<TableBody>
<TableRow>
<Formik
initialValues={{tasks: [
{
name: "Build a table w/ Formik",
company: "Alchemy",
monday: "2",
tuesday: "1.2",
wednesday: "3.2",
thursday: "0",
friday: "0",
},
{
name: "Build something else",
company: "Alchemy",
monday: "2",
tuesday: "0",
wednesday: "0",
thursday: "0",
friday: "0",
}
]}}
onSubmit={values =>
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
}, 500) }
render={({values}) => (
<Form>
<FieldArray
name="tasks"
render={arrayHelpers => (
<div>
{values.tasks && values.tasks.length > 0 ? (
values.tasks.map((task, index) => (
<TableRow key={index}>
<TableCell component="th" scope="row">
<TextField
name={`tasks`}
className={classes.textField}
/>
</TableCell>
</TableRow>
))
) : null}
</div>
)}
/>
</Form>
)}
/>
</TableRow>
</TableBody>
It creates a table and recognizes that there are two tasks in the array, but does not display the input fields.
I recommend react-hook-form and using component="form"
<TableRow
component="form"
noValidate
autoComplete="off"
onSubmit={handleSubmit(data => console.log(data))}
>
Use the useFormik() hook. With it you can do something like..
const formik = useFormik(){
initialValues:[],
onSubmit: (v) => {}
etc...
}
With this way Formik not gonna be a child of a tablebody tag!