Error: Cannot read property 'map' of undefined (state) inside render method - javascript

In my project I have a table from react material ui. I am getting an error while filling this table when I use data from state to fill the table. The error is cannot read property 'map' of undefined. The line where it occurs will be marked in the code snippet below.
Here is my code (the error is in the JSX render method):
class AllBooks extends React.Component {
constructor(props) {
super(props);
this.state = {
rows: []
};
}
componentDidMount() {
console.log('component mounted!');
axios.get('http://localhost:8080/all-books').then(allBooks => {
console.log(allBooks.data);
this.setState({
rows: allBooks
});
});
}
onAddButtonClick = (row) => {
console.log('onAddButton click!');
console.log(row);
//axios.post('http://localhost:8080/addBookToRentedBooks', { email: this.state.email,
title: this.state.title, message: this.state.message }).then();
}
test = () => {
console.log(this.state.rows.data);
this.state.rows.data.map(row => console.log(row));
}
render() {
return (
<div>
<Grid container spacing={3}>
<Grid item xs={6} sm={3}></Grid>
<Grid item xs={6} sm={6}>
<h1 style={{textAlign: 'center'}}>All Books</h1>
<br /><br />
<TableContainer component={Paper}>
<Table aria-label="simple table">
<TableHead>
<TableRow>
<TableCell >Title</TableCell>
<TableCell align="right">Author</TableCell>
<TableCell align="right">ISBN</TableCell>
<TableCell align="right">Year</TableCell>
<TableCell align="center">Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{this.state.rows.data.map((row) => (
<TableRow key={row.name}>
<TableCell component="th" scope="row">
{row.name}
</TableCell>
<TableCell align="right">z</TableCell>
<TableCell align="right">z</TableCell>
<TableCell align="right">z</TableCell>
<TableCell align="center">
<IconButton onClick={ () => this.onAddButtonClick(row) }>
<AddCircleOutlineIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</Grid>
<Grid item xs={6} sm={3}><button onClick={this.test}>click me</button></Grid>
</Grid>
</div>
)
}
}
I wonder why this.state.rows.data is marked as undefined because I added a simple button - when i click this button, the test method gets called and prints out this.state.rows correctly.
What did I do wrong?
Thanks for every help!

Because initial of state rows is []. so when fetching data this.state.rows.data will be undefined.
You can use optional chaining to check it has value or not before using map:
this.state.rows.data?.map(...)

Related

How do I map over data from API in a collapsible table?

I don't know if the question captures what I had in my but I will explain below...
I fetched data from API and mapped into a collapsible table. Full details of the data should be embedded in EACH row such that onclick on each row, reveals the full details. Here's the code below
function Encounter() {
const [open2, setOpen2] = useState(false);
const [details, setDetails] = useState([]);
const getDetails = async () => {
try {
const fetch = await Axios.get(
"https://pshs3.herokuapp.com/all/encounter"
);
setDetails(fetch.data.data)
} catch (err) {
console.log(err);
}
};
useEffect(() => {
getDetails();
}, []);
return (
<Wrapper>
<Table stickyHeader aria-label="sticky table">
<TableHead>
<TableRow>
<TableCell/>
<TableCell >
Enrollment ID
</TableCell>
<TableCell >
Encounter
</TableCell>
<TableCell>
Facility Code
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{details.map((detail, idx) => {
return (
<>
<TableRow sx={{ "& > *": { borderBottom: "unset" } }}>
<TableCell>
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? (
<KeyboardArrowUpIcon />
) : (
<KeyboardArrowDownIcon />
)}
</IconButton>
</TableCell>
<TableCell key={idx}>
{detail.enrollment_id}
</TableCell>
<TableCell key={idx}>
{detail.encounter}
</TableCell>
<TableCell key={idx}>
{detail.faciity_code}
</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">
Details
</Typography>
<Tooltip />
</Box>
</Collapse>
</TableCell>
</TableRow>
</>
);
})}
</TableBody>
</Table>
</Wrapper>
);
}
export default Encounter;
The problem I have is how to implement the open and setOpen state to individual row, also the Tooltip component(which is a the full table details from the API) to display full details of each row onclick which should correspond to the selected row in question.
Here are two solutions to the first problem.
1. Create a separate component for each <TableRow />
This component will have its own state and allows you to collapse/expand each row individually.
2. Use a dictionary for the open state
Since you have multiple (dynamic) rows, you can introduce a dictionary for the open state.
const [open, setOpen] = useState({});
For each row, you will use the open[idx] property to determine if the row should be "open"
<Collapse in={open[idx]} timeout="auto" unmountOnExit>
And in the <IconButton /> component, set the state based on the current row state.
<IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(current => ({ ...current, [idx]: !current[idx] }))}
>
Firstly add a variable in your details object:
const getDetails = async () => {
try {
const fetch = await Axios.get(
"https://pshs3.herokuapp.com/all/encounter"
);
const response = fetch.data.data;
response.map((elem) => elem.open = false)
setDetails(response)
} catch (err) {
console.log(err);
}
};
Then you can change the open variable for each element in details:
<IconButton
aria-label="expand row"
size="small"
onClick={() => detail.open = !detail.open)}
>
You might need to update the state, so change your onClick:
onClick={() => changeOpenStatus(idx))}
and the function:
const changeOpenStatus = (idx) => {
const newDetails = {...details}
newDetails[idx].open = !newDetails[idx].open;
setDetails(newDetails)
}

Render data as a functional component with React.js and Material-UI

I have a problem with the management of the data, when i try to render some data from the pokemon api my table render multiple times the titles, i tried to move only the data to a different component but not luck.
How can i fix this?
API CAll
export const PokemonApi = () => {
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<>
{
poke.map(( info, name ) => {
return <Lista key={ name } info={ info } />
})
}
</>
)
}
component where I try to render
export const Lista = (props) => {
const classes = useStyles();
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}
This is the page where i render the table
import React from 'react'
import { PokemonApi } from '../api/PokemonApi'
export const Pokes = () => {
return (
<>
<PokemonApi />
</>
)
}
And here is the table.
I hope anyone can help me!
As your code is written, you are not rendering one table with a row for each line. You are creating one Lista par record, you have as many tables as pokemon.
What you are looking to achieve is more like :
function PokemonRow(props) {
return (
<TableRow key={ props.info.name }>
<TableCell component="th" scope="row">
{ props.info.name }
</TableCell>
<TableCell align="right">{ props.info.url }</TableCell>
</TableRow>
)
}
export const PokemonTable() {
const classes = useStyles();
const [poke, setPoke] = useState([]);
const data = () => {
axios.get('https://pokeapi.co/api/v2/pokemon?limit=10&offset=20').then(( response ) => {
setPoke(response.data.results);
console.log(response.data.results);
})
.catch( err => {
console.log(err);
})
}
useEffect(() => {
data()
}, []);
return (
<div>
<Container maxWidth="md">
<TableContainer component={Paper}>
<Table className={ classes.table } size="small" aria-label="a dense table">
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell align="right">URL</TableCell>
</TableRow>
</TableHead>
<TableBody>
{poke.map(infos => <PokemonRow info={infos}/>)}
</TableBody>
</Table>
</TableContainer>
</Container>
</div>
)
}

Table - TypeError: Cannot read property 'map' of undefined Reactjs

I am using material-ui table. I can map the items and display it already however, mapping the orders.items in the table will cause this error:
TypeError: Cannot read property 'map' of undefined
These are the codes:
const [orders, setOrders] = useState([]);
{orders.map((order) => (
<div key={order.id}>
<Typography variant="h6">Ship By:{order.id}</Typography>
<TableContainer>
<Table className={classes.table} aria-label="spanning table">
<TableHead>
<TableRow>
<TableCell align="center" colSpan={3}>
Details
</TableCell>
<TableCell align="right">Price</TableCell>
</TableRow>
<TableRow>
<TableCell>Desc</TableCell>
<TableCell align="right">Qty.</TableCell>
<TableCell align="right">Unit</TableCell>
<TableCell align="right">Sum</TableCell>
</TableRow>
</TableHead>
<div>
<-------------------This is where the error is----------->
<TableBody>
{orders &&
orders.items.map((item) => (
<TableRow key={item.documentID}>
<TableCell>{item.productName}</TableCell>
</TableRow>
))}
</TableBody>
<-------------------This is where the error is----------->
</div>
</Table>
</TableContainer>
</div>
this is the JSON object
[
{
id: "IYQss7JM8LS4lXHV6twn",
total: 130,
items: [
{
qty: 1,
productPrice: "130",
documentID: "y8PN1cUyIr1sBnyV3Iqj",
productName: "Item1",
},
],
displayName: "Jennie Jenn",
},
];
So at your first render, you will not have items in orders array because it is blank array so when it try to map on order.items it will give error.
To solve this try to use optional chaining like below:-
<TableBody>
{order &&
order.items?.map((item) => (
<TableRow key={item.documentID}>
<TableCell>{item.productName}</TableCell>
</TableRow>
))}
</TableBody>

Resetting state based upon choice

I have a custom component that allows for my dropdowns to be generated via the backend, however i need to somehow store the two dropdown values in state, currently i am setting contractType to a value of 10 to meet the conditions of my conditional below, however when selecting a second value which is not 10 state is not reset, how could i achieve a change between two separate state values?
my code
const [contractType, setContractType] = useState(10);
function handleChange(event) {
setContractType(oldValues => ({
...oldValues,
[event.target.name]: event.target.value,
}));
}
my component below
<Search
type="procurementType"
name="procurementType"
label="Procurement Type"
id="procurementTypeId"
onChange={handleChange}
value={contractType}
/>
finally my if statement relating to the decision
{contractType == 10 ? <div className={classes.tableWrapper}>
<Table className={classes.table}>
<TableHead>
<TableRow >
<TableCell>ID</TableCell>
<TableCell>Internal Ref</TableCell>
<TableCell>Title</TableCell>
<TableCell>Organisation</TableCell>
</TableRow>
</TableHead>
<TableBody>
{localContracts.map(row => (
<TableRow className={classes.tableRow}>
<TableCell className={classes.tableCell}>{row.id}</TableCell>
<TableCell>{row.internalRef}</TableCell>
<TableCell>{row.title}</TableCell>
<TableCell>{row.organisationId}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div> : <Table className={classes.table}>
<TableHead>
<TableRow >
<TableCell>ID</TableCell>
<TableCell>Internal Ref</TableCell>
<TableCell>Title</TableCell>
<TableCell>Organisation</TableCell>
</TableRow>
</TableHead>
<TableBody>
{frameworkContracts.map(row => (
<TableRow className={classes.tableRow}>
<TableCell className={classes.tableCell}>{row.id}</TableCell>
<TableCell>{row.internalRef}</TableCell>
<TableCell>{row.title}</TableCell>
<TableCell>{row.organisationId}</TableCell>
</TableRow>
))}
</TableBody>
</Table>}

Embedding a Menu inside a TableCell with Material-UI

I would like to implement Google's Material UI Menu Item inside of a TableCell, as shown in their docs here, as seen below:
Here is my current approach:
import React from 'react';
import PropTypes from 'prop-types';
import { withStyles } from '#material-ui/core/styles';
import {
Grid,
IconButton,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Paper,
Menu,
MenuItem,
Button,
} from '#material-ui/core';
import { ExpandLess, ExpandMore } from '#material-ui/icons';
const styles = theme => ({});
const Questions = ({ data, classes, openMenu, anchorEls, handleClose }) => {
const CustomTableCell = withStyles(theme => ({
head: {
backgroundColor: theme.palette.common.black,
color: theme.palette.common.white,
},
body: {
fontSize: 14,
},
}))(TableCell);
const formatData = rawData => Object.keys(rawData).map(key => rawData[key]);
const n = { key: 'hi', rating: 55, text: 'wassup' };
return (
<Grid container alignItems={'center'} direction={'column'} spacing={8}>
<Paper className={classes.root}>
<Button
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={e => openMenu('dude', e)}
>
<ExpandMore />
</Button>
<Menu
id={`dude`}
key="menu"
anchorEl={anchorEls.dude}
open={Boolean(anchorEls.dude)}
onClose={e => handleClose('dude', e)}
>
<MenuItem onClick={e => handleClose('dude', e)}>Delete</MenuItem>
<MenuItem onClick={e => handleClose('dude', e)}>Flag</MenuItem>
<MenuItem onClick={e => handleClose('dude', e)}>
Mark Answered
</MenuItem>
</Menu>
<Table className={classes.table}>
<TableHead>
<TableRow>
<CustomTableCell>Question</CustomTableCell>
<CustomTableCell numeric>Rating</CustomTableCell>
<CustomTableCell>Upvote</CustomTableCell>
<CustomTableCell>Downvote</CustomTableCell>
<CustomTableCell>Options</CustomTableCell>
</TableRow>
</TableHead>
<TableBody>
<TableRow className={classes.row} key={n.key}>
<CustomTableCell component="th" scope="row">
{n.text}
</CustomTableCell>
<CustomTableCell numeric>{n.rating}</CustomTableCell>
<CustomTableCell>
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={() => ''}
>
<ExpandLess />
</IconButton>
</CustomTableCell>
<CustomTableCell>
<IconButton
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={() => ''}
>
<ExpandMore />
</IconButton>
</CustomTableCell>
<CustomTableCell>
<Button
key="close"
aria-label="Close"
color="inherit"
className={classes.close}
onClick={e => openMenu(n.key, e)}
>
<ExpandMore />
</Button>
<Menu
id={`simple-menu-${n.key}`}
key="menu"
anchorEl={anchorEls[n.key]}
open={Boolean(anchorEls[n.key])}
onClose={e => handleClose(n.key, e)}
>
<MenuItem onClick={e => handleClose(n.key, e)}>
Delete
</MenuItem>
<MenuItem onClick={e => handleClose(n.key, e)}>dude</MenuItem>
<MenuItem onClick={e => handleClose(n.key, e)}>choc</MenuItem>
</Menu>
</CustomTableCell>
</TableRow>
</TableBody>
</Table>
</Paper>
</Grid>
);
};
Questions.propTypes = {
data: PropTypes.object.isRequired,
anchorEls: PropTypes.object.isRequired,
classes: PropTypes.object.isRequired,
openMenu: PropTypes.func.isRequired,
handleClose: PropTypes.func.isRequired,
};
Questions.defaultProps = {};
export default withStyles(styles)(Questions);
This is working, however the menu is not appearing in the correct place, even when passing in the related event e. I put in a dummy element before the table to test whether or not the table was affecting the menu, and found that the dummy worked just fine, as you can see in the screenshot below.
And the improperly placed menu from the button in the table:
Any ideas on what could be happening? Obviously, the context and anchorEl isn't correctly identifying the location on the page, but not sure what to do to combat that.
Problem in your openMenu function.You need to set the event target in your openMenu function, and set null in handleClose function. I am giving here a little example which may help you:
class DemoList extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
anchorEls: []
}
}
handleActionClick = (id, event) => {
let { anchorEls } = this.state;
anchorEls[id] = event.target;
this.setState({ anchorEls });
}
handleActionClose = (id, event) => {
let { anchorEls } = this.state;
anchorEls[id] = null;
this.setState({ anchorEls });
}
render() {
let { classes } = this.props;
const { data, anchorEls } = this.state;
return (
<Paper className="main">
<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">Carbs (g)</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map(row => (
<TableRow key={row.id}>
<TableCell component="th" scope="row"> {row.name} </TableCell>
<TableCell align="right">{row.calories}</TableCell>
<TableCell align="right">{row.fat}</TableCell>
<TableCell align="right">{row.carbs}</TableCell>
<TableCell align="right">
<IconButton
aria-label="more"
aria-controls="long-menu"
aria-haspopup="true"
onClick={e => this.handleActionClick(row.id, e)}
>
<MoreVert />
</IconButton>
<Menu
id={row.id}
anchorEl={anchorEls[row.id]}
keepMounted
open={Boolean(this.state.anchorEls[row.id])}
onClose={e => this.handleActionClose(row.id, e)}
>
<MenuItem onClick={e => this.handleActionClose(row.id, e)}> View Details </MenuItem>
<MenuItem onClick={e => this.handleActionClose(row.id, e)}> Assign </MenuItem>
</Menu>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Paper>
)
}
}
After hacking away I found that removing the CustomTableCell declaration and changing all the CustomTableCells to normal TableCells resolved the problem. 😫 Not sure why that would fix it given that the CustomTableCell code was pulled straight from the Demos page.

Categories

Resources