I'm trying to render a component that uses an array of filters to make a list. I pass the array as an argument to the function but it returns an error. The array is not null or undefined because it shows the itens if I log to the console.
Console.log returns:
Here is my code:
const List = (filtrosPorTipo) => {
let filtros = filtrosPorTipo[0]
let valoresDosFiltros = filtrosPorTipo[1]
let lista = (
<List>
{filtros.map((item, index) => {
return (
<>
<ListItem
button
onClick={() => setOpen({ [item]: !open[item] })}
key={item}
>
<ListItemText primary={item} />
{open[item] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open[item]} timeout='auto'>
<List component='div' disablePadding>
{valoresDosFiltros[index].map((filtro) => {
return (
<>
<ListItem key={filtro}>
<p>{`${filtro}\n`}</p>
<ListItemSecondaryAction>
<Checkbox
label={filtro}
key={filtro.toString()}
color='primary'
onChange={() => handleChecked(filtro)}
checked={checked[filtro]}
/>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</>
)
})}
</List>
</Collapse>
</>
)
})}
</List>
)
return lista
}
Error:
Perhaps the component it's trying to render before "filtros" is assigned. It's a common and logical behavior in React.
Try adding a conditional before the .map() call:
{filtros ? filtros.map((item, index) => {
return (
<>
<ListItem
button
onClick={() => setOpen({ [item]: !open[item] })}
key={item}
>
<ListItemText primary={item} />
{open[item] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open[item]} timeout='auto'>
<List component='div' disablePadding>
{valoresDosFiltros[index].map((filtro) => {
return (
<>
<ListItem key={filtro}>
<p>{`${filtro}\n`}</p>
<ListItemSecondaryAction>
<Checkbox
label={filtro}
key={filtro.toString()}
color='primary'
onChange={() => handleChecked(filtro)}
checked={checked[filtro]}
/>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</>
)
})}
</List>
</Collapse>
</>
)
}) : null}
You can map over the values when they are present like so. If you are not planning on displaying a Loading screen in the process of waiting for the data then this would work. Otherwise use a ternary operator like the other answer.
{filtros && filtros.map((item, index) => {
return (
<>
<ListItem
button
onClick={() => setOpen({ [item]: !open[item] })}
key={item}
>
<ListItemText primary={item} />
{open[item] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open[item]} timeout='auto'>
<List component='div' disablePadding>
{valoresDosFiltros[index].map((filtro) => {
return (
<>
<ListItem key={filtro}>
<p>{`${filtro}\n`}</p>
<ListItemSecondaryAction>
<Checkbox
label={filtro}
key={filtro.toString()}
color='primary'
onChange={() => handleChecked(filtro)}
checked={checked[filtro]}
/>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</>
)
})}
</List>
</Collapse>
</>
)
})}
While rendering the component you have to check data in filtrosPorTipo. It has an array value or not. Like below:
const List = (filtrosPorTipo) => {
if (filtrosPorTipo && filtrosPorTipo.length > 0) {
let filtros = filtrosPorTipo[0]
let valoresDosFiltros = filtrosPorTipo[1]
let lista = (
<List>
{filtros.map((item, index) => {
return (
<>
<ListItem
button
onClick={() => setOpen({ [item]: !open[item] })}
key={item}
>
<ListItemText primary={item} />
{open[item] ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open[item]} timeout='auto'>
<List component='div' disablePadding>
{valoresDosFiltros[index].map((filtro) => {
return (
<>
<ListItem key={filtro}>
<p>{`${filtro}\n`}</p>
<ListItemSecondaryAction>
<Checkbox
label={filtro}
key={filtro.toString()}
color='primary'
onChange={() => handleChecked(filtro)}
checked={checked[filtro]}
/>
</ListItemSecondaryAction>
</ListItem>
<Divider />
</>
)
})}
</List>
</Collapse>
</>
)
})}
</List>
)
return lista
}
return 'No data available!'
}
Related
I'm getting the warning message validateDOMNesting(...): cannot descendant of li
in the console and it shows the error is on the treeview and the timeline I dont know where to fix the code, the below code is facing that warning is there any solution for this?
<Box>
<TreeView >
<Timeline key={comment.id}>
<TimelineItem>
<TimelineSeparator>
<Avatar {...stringAvatar(comment.username) } />{" "}
<TimelineConnector />
</TimelineSeparator>
<TimelineOppositeContent style={{maxWidth: "1px",paddingLeft: "0px",paddingRight: "0px", }}/>
<TimelineContent>
<Stack direction="row" spacing={1}>
<Typography variant="h6" sx={{fontSize:"15px" ,fontWeight:"bold"}}>
{comment.username.charAt(0).toUpperCase() + comment.username.slice(1)}
</Typography>
<Item variant="span" sx={{fontSize:"10px"}}> {createdAt}</Item>
</Stack>
<ReactMarkdown children={comment.body} rehypePlugins={[rehypeRaw]} />
<TreeItem
nodeId="1" label={
<TimelineContent>
{canReply && (<Chip label="Reply" onClick={() => setActiveComment({ id: comment.id, type: "replying", }) }
sx={{fontSize:"10px",mb:1}} size="small" icon={<ReplyIcon /> }/> )}
{isReplying && (
<CommentForm submitLabel="Reply" hasCancelButton handleSubmit={(text) => addComment(text, replyId)} handleCancel={() => { setActiveComment(null);}} />)}
</TimelineContent> } >
<TreeItem
nodeId="2"
label={
<Timeline>
{replies.length > 0 && (
<div>
{replies.map((reply) => (
<Comment
comment={reply}
key={reply.id}
setActiveComment={setActiveComment}
activeComment={activeComment}
addComment={addComment}
parentId={comment.id}
replies={[]}
currentUserId={currentUserId}
/>
))}
</div>
)}
</Timeline>}/>
</TreeItem>
</TimelineContent>
</TimelineItem>
</Timeline>
</TreeView>
</Box>
I have table that I can expand Collapse material-ui. Everything is worked perfectly.
But there is one thing I want to update that when I click to expand there is only one expand collapse opened at a time.
const InvoiceInfo = ({el}) => {
const [open, setOpen] = React.useState(false)
}
function handleExpand() {
setOpen(!open)
}
return (
<>
<TableRow key={el.id}>
<TableCell>
{el.name}
</TableCell>
<TableCell>
<IconButton onClick={handleExpand}>
{open ? (
<KeyboardArrowUp fontSize="large" />
) : (
<KeyboardArrowDown fontSize="large" />
)}
</IconButton>
</TableCell>
</TableRow>
{
<TableRow>
<Collapse in={open} timeout="auto" unmountOnExit>
<div>
EXPAND ROW
</div>
</Collapse>
</TableRow>
}
</>
)
Above is my code looks like.
Any advice is very meaningful to me. Thank you so much.
You can use a simple state based on the index of an array or a given number / value:
const [isOpenCollapse, setIsOpenCollapse] = useState(null);
const handleOpen = (clickedIndex) => {
if (isOpenCollapse === clickedIndex) {
setIsOpenCollapse(null);
} else {
setIsOpenCollapse(clickedIndex);
}
};
[...]
return (
<List>
{arr.map((elm, index) => {
return (
<>
<ListItem key={elm.ID} onClick={() => handleOpen(index)} id="listItem">
{isOpenCollapse === index ? <ExpandLess /> : <ExpandMore />}
<Box>
<Typography>{elm.label}</Typography>
</Box>
</ListItem>
<Collapse in={isOpenCollapse === index} timeout="auto" unmountOnExit>
<p>PUT SOMETHING HERE</p>
</Collapse>
</>
);
})}
</List>;
)
I am using the React Material-UI library. I have a search 'filter' on the main/parent page, which should pop open a Drawer that is located in a separate file.
I understand how this all works within one file, but I cannot understand how to break this into separate files (obviously for code clarity intent). I also cannot tell how I get the 'variables' out of the Drawer for items I select. Below I have pasted my two files, whereby I would like to get the 'onclick' in the parent to launch the Drawer in the child file. Any assistance would be excellent!
My parent file:
return (
<Autocomplete
id="search"
...
renderInput={(params) => (
<TextField
{...params}
label="Search for an item"
variant="outlined"
InputProps={{
...params.InputProps,
startAdornment: (
<InputAdornment position="start">
<IconButton color="primary" aria-label="filters" component="span">
<TuneRoundedIcon onClick={ handleFilterOpen } />
</IconButton>
<Filter open={ openFilter} />
</InputAdornment>
),
endAdornment: (
<React.Fragment>
{loading ? <CircularProgress color="inherit" size={20} /> : null}
{params.InputProps.endAdornment}
</React.Fragment>
),
}}
fullWidth
/>
)}
And this is the Filter.tsx (my child file):
export default function Filter() {
const classes = useStyles();
const theme = useTheme();
const [open, setOpen] = React.useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<Drawer
className={classes.drawer}
variant="persistent"
anchor="right"
open={open}
classes={{
paper: classes.drawerPaper,
}}
>
<div className={classes.drawerHeader}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === 'rtl' ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</IconButton>
</div>
<Divider />
<List>
{['Inbox', 'Starred', 'Send email', 'Drafts'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
<Divider />
<List>
{['All mail', 'Trash', 'Spam'].map((text, index) => (
<ListItem button key={text}>
<ListItemIcon>{index % 2 === 0 ? <InboxIcon /> : <MailIcon />}</ListItemIcon>
<ListItemText primary={text} />
</ListItem>
))}
</List>
</Drawer>
);
}
You need to pass the open state down to the child component via props. The child component should not have an "open" state. I've simplified this for you for better understanding:
const Parent = () => {
const [open, setOpen] = React.useState(false);
const toggleOpen = () => {
setOpen(!open)
}
const customFunction(valueFromChildComponent) {
alert(valueFromChildComponent); // this alerts "myValue" since "myValue" is passed from child component
}
return (
<Child isOpen={open} myCustomFunction={customFunction} />
);
}
Child component
const Child = props => {
return (
console.log(props.isOpen) // props.isOpen contains the value passed down from the parent
<button onClick={props.myCustomFunction("myValue")}>test Button</button>
);
}
I've also added an example how you could pass up a value form your child component. in my case i passed up a string but you could for example pass up the selected value of a drop down or basically whatever you want.
Working with material-ui v3.9.4, react-router v3. Created a left nav bar with a List containing Link items to redirect to another page. For some reason when I click on an item, the page is redirected, but the state is not updated until the second click to show the active item. Any idea as to why that is? Code below. I've console logged the setState change, which doesnt print until the second click.
class LeftNavigation extends Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
sidebarCollapsed: false,
}
}
handleListItemClick = (event, index) => {
console.log("CLICKED"); //prints first click
this.setState({ selectedIndex: index }, () => {
console.log("SELECTED INDEX:: ", this.state.selectedIndex) //prints second click
});
}
handleCollapse = () => {
this.setState({ sidebarCollapsed: !this.state.sidebarCollapsed })
}
render() {
const { classes } = this.props;
return (
<div className={this.state.sidebarCollapsed ? classes.rootCollapsed : classes.root}>
<List component="nav">
<ListItem
button
component={props => <Link to="./pageOne" {...props} />}
selected={this.state.selectedIndex === 0}
onClick={(event) => this.handleListItemClick(event, 0)}
>
<ListItemIcon>
<CalendarIcon />
</ListItemIcon>
<ListItemText classes={{ primary: classes.listText }} primary="Page One" />
</ListItem>
<ListItem
button
onClick={(event) => this.handleListItemClick(event, 1)}
component={props => <Link to="./pageTwo" {...props} />}
selected={this.state.selectedIndex === 1}
>
<ListItemIcon>
<MailIcon />
</ListItemIcon>
<ListItemText classes={{ primary: classes.listText }} primary="Page Two" />
</ListItem>
<ListItem
button
component={props => <Link to="./pageThree" {...props} />}
selected={this.state.selectedIndex === 2}
onClick={(event) => this.handleListItemClick(event, 2)}
>
<ListItemIcon>
<NotesIcon />
</ListItemIcon>
<ListItemText classes={{ primary: classes.listText }} primary="Page Three" />
</ListItem>
</List>
<div
onClick={this.handleCollapse}>
{this.state.sidebarCollapsed ? <ChevronRight /> :
<div><ChevronLeft /><p>Collapse sidebar</p></div>}
</div>
</div>
)
}
}
export default withStyles(styles)(LeftNavigation );
When I try to select a specific button rendered from a 2d array, it ends up selecting all rendered buttons. Is there a way to render the lower details for one specific button.
<Grid container>
{this.serviceByLangauge.map((cat, id) => (
<Grid
item
xs={4}
key={cat.id}
className={this.props.classes.categoryGroup}
>
<Button
variant="contained"
size="medium"
color="primary"
className={this.props.classes.services}
onClick={this.showServices.bind(this, cat.services)}
key={cat.id}
>
{cat.name}
</Button>
<div className={this.props.classes.serviceGroup}>
{servicesList.length > 0 ? (
<List>
{servicesList.map((serv, id) => (
<ListItem key={id}>
<ListItemText primary={id + 1 + ") " + serv.name} />
</ListItem>
))}
</List>
) : (
<div></div>
)}
</div>
</Grid>
))}
</Grid>
]2
can you show us the data of the 2d array?
edit:
I guess you're not iterating servicesList.
Try this instead:
<Grid container>
{this.serviceByLangauge.map((cat, id) => (
<Grid
item
xs={4}
key={cat.id}
className={this.props.classes.categoryGroup}
>
<Button
variant="contained"
size="medium"
color="primary"
className={this.props.classes.services}
onClick={this.showServices.bind(this, cat.services)}
key={cat.id}
>
{cat.name}
</Button>
<div className={this.props.classes.serviceGroup}>
{cat.services.length > 0 ? (
<List>
{cat.services.map((serv, id) => (
<ListItem key={id}>
<ListItemText primary={id + 1 + ") " + serv.name} />
</ListItem>
))}
</List>
) : (
<div></div>
)}
</div>
</Grid>
))}
</Grid>
From the images and the source code you provided, I think to following is wrong:
You derive the services to show from your state. In your state, you have only one list of services. When clicking a button, you update the state with the services of the button.
<Button
variant="contained"
size="medium"
color="primary"
className={this.props.classes.services}
onClick={this.showServices.bind(this, cat.services)}
key={cat.id}
>
Then you iterate on this same state for every button:
<div className={this.props.classes.serviceGroup}>
{servicesList.length > 0 ? (
<List>
{servicesList.map((serv, id) => (
<ListItem key={id}>
<ListItemText primary={id + 1 + ") " + serv.name} />
</ListItem>
))}
</List>
) : (
<div></div>
)}
</div>
I think you want to iterate over cat instead of serviceList:
<div className={this.props.classes.serviceGroup}>
{cat.length > 0 ? (
<List>
{cat.map((serv, id) => (
<ListItem key={id}>
<ListItemText primary={id + 1 + ") " + serv.name} />
</ListItem>
))}
</List>
) : (
<div></div>
)}
</div>
I had to add a condition to make sure that I'm only rendering the div when category id's match
if (this.state.showThis) {
servicesComponent = (
<List className={this.props.classes.serviceGroup}>
{this.state.services.map((serv, id) => (
<ListItem key={id}>
<ListItemText primary={id + 1 + ") " + serv[langCode]} />
</ListItem>
))}
</List>
);
} else {
servicesComponent = <div></div>;
}
const categories = servicesAndCategories.map((category, index) => (
<Grid
item
xs={4}
className={this.props.classes.categoryGroup}
key={index}
>
<Button
variant="contained"
size="medium"
color="primary"
className={this.props.classes.services}
onClick={this.showServices.bind(this, category.services, category.id)}
>
{category[langCode]}
</Button>
{this.state.currentCategory === category.id && servicesComponent}
</Grid>
));
``