I made this component, userRow:
userRow:
export default function UserRow(props, {data}) {
const style = styles();
const userList = data.map((row) => {
return { name: row, details: row };
});
return (
<div>
{userList.map(row => (
<List className={style.listSize}>
<ListItem className={style.listSize} >
<ListItemIcon className={style.listSize}>
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{props.userList.name}</ListItemText>
<Link to={`/users/${props.userList.details}`}>
<ListItemIcon className={style.listSize}>
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
</List>
))}
</div>
)
}
I want to call it in another component, let's say in the users component:
users:
export default function User({ data }) {
const userList = data.map((row) => {
return { name: row, details: row };
});
return (
<div>
<UserRow name={userList} details={userList} />
</div>
)
}
Data is a json:
3) ["Philip J. Fry", "Turanga Leela", "Bender Bending RodrÃguez"]
0: "Philip J. Fry"
1: "Turanga Leela"
2: "Bender Bending RodrÃguez"
length: 3
__proto__: Array(0)
My problem is i get Cannot read property 'map' of undefined, i tried everything i know.
I want to call the userRow component on the users component and display the data.
I'd do something like this:
export default function UserRow({ name, details }) {
const style = styles();
return (
<ListItem className={style.listSize} >
<ListItemIcon className={style.listSize}>
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{name}</ListItemText>
<Link to={`/users/${details}`}>
<ListItemIcon className={style.listSize}>
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
)
}
export default function User({ data }) {
// creates the user list here
const userList = data.map((row) => {
return { name: row, details: row };
});
return (
<List className={style.listSize}>
{userList.map(row => (
<UserRow name={row} details={row} />
))}
</List>
)
}
So, have the UserRow component to be JUST the row, not the full list and have the logic of mapping and creates the userList on the parent component/container.
You need to access the props differently since the props are accumulated in the props object:
export default function User({ data }) {
return (
<div>
<List className={style.listSize}>
{
data.map(row => <UserRow name={row} details={row}/>}
}
</List>
</div>
)
}
And access it like this:
export default function UserRow({details}) {
const style = styles();
return (
<ListItem className={style.listSize} >
<ListItemIcon className={style.listSize}>
<PeopleIcon className={style.color}/>
</ListItemIcon>
<ListItemText className={style.text}>{props.name}</ListItemText>
<Link to={`/users/${props.details}`}>
<ListItemIcon className={style.listSize}>
<ListAltIcon className={style.iconColor}/>
</ListItemIcon>
</Link>
</ListItem>
)
}
Related
I want the category details in the parent categoryHandler function
from the child component. I don't know where to place this
props.categoryHandler function in the child component so that when it
is clicked I should get the details to the parent categoryHandler
function.
PARENT COMPONENT:
const categoryHandler = (catg) => {
console.log(catg);
}
<div className="categoryBox">
<Category categories={categories} categoryHandler={() => categoryHandler} />
</div>
CHILD COMPONENT:
export default function Category({ categories }) {
if (categories.length) {
const menu = recursiveMenu(categories);
return menu.map((item, key) => <MenuItem key={key} item={item} />);
} else {
return <div>No Menus</div>
}
}
const MenuItem = ({ item }) => {
const Component = hasChildren(item) ? MultiLevel : SingleLevel;
return <Component item={item} />;
};
const SingleLevel = ({ item }) => {
return (
<ListItem button>
<ListItemText className="category-link child" primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item }) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemText className="category-link parent" primary={item.title} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
Your approach in the code is right, just you have to modify two thing to achieve what you are expecting.
In the parent component you have to modify passing the function as a props like this:
categoryHandler={categoryHandler}
In the child component you have to catch the function while destructuring the props and call in on the both list item with the single item as function parameter:
add the function in props destructuring and pass the function as another props to MenuItem
export default function Category({ categories, categoryHandler }) {
if (categories.length) {
const menu = recursiveMenu(categories);
return menu.map((item, key) => <MenuItem categoryHandler={categoryHandler} key={key} item={item} />);
} else {
return <div>No Menus</div>
}
}
Now again pass the function props to Single And MultiLevel List and call the function on both place:
const MenuItem = ({ item, categoryHandler }) => {
const Component = hasChildren(item) ? MultiLevel : SingleLevel;
return <Component item={item} categoryHandler={categoryHandler} />;
};
const SingleLevel = ({ item, categoryHandler }) => {
return (
<ListItem button onClick={handleClick}>
<ListItemText className="category-link child" primary={item.title} />
</ListItem>
);
};
const MultiLevel = ({ item, categoryHandler}) => {
const { items: children } = item;
const [open, setOpen] = useState(false);
const handleClick = () => {
setOpen((prev) => !prev);
};
return (
<React.Fragment>
<ListItem button onClick={handleClick}>
<ListItemText className="category-link parent" primary={item.title} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{children.map((child, key) => (
<MenuItem key={key} item={child} />
))}
</List>
</Collapse>
</React.Fragment>
);
};
This solution should work fine!
When you call your function passed as a prop, you can pass data from a child to parent component.
Parent Component:
const categoryHandler = (catg) => {
console.log(catg);
}
<div className="categoryBox">
<Category categories={categories} categoryHandler={categoryHandler} />
</div>
Child Component:
export default function Category(props) {
props.categoryHandler(data);
}
You can need to pass the parameter to the function in your parent component like this:
<Category categories={categories} categoryHandler={categoryHandler} />
You need to pass the prop categoryHandler all the way to the SingleLevel like this:
categoryHandler={categoryHandler}
You can need to add onClick to the ListItem in your SingleLevel component with item parameter like this:
<ListItem button onClick={() => categoryHandler(item)}>
You can take a look at this sandbox for a live working example of this solution.
I'm designing the frontend of a chat application and this is the Conversations component.
The problem is on line 87 and I've highlighted the line. 'conversations' is an array so I'm unable to figure out the issue here. I'm actually quite new to react so apologies if it's something silly. Thanks!
const Conversations = (props) => {
const classes = useStyles();
const [conversations, setConversations] = useState([]);
const [newConversation, setNewConversation] = useState(null);
const getConversations = useGetConversations();
useEffect(() => {
getConversations().then((res) => setConversations(res));
}, [newConversation]);
useEffect(() => {
let socket = socketIOClient(process.env.REACT_APP_API_URL);
socket.on("messages", (data) => setNewConversation(data));
return () => {
socket.removeListener("messages");
};
}, []);
return (
<List className={classes.list}>
<ListItem
classes={{ root: classes.subheader }}
onClick={() => {
props.setScope("Global Chat");
}}
>
<ListItemAvatar>
<Avatar className={classes.globe}>
<LanguageIcon />
</Avatar>
</ListItemAvatar>
<ListItemText className={classes.subheaderText} primary="Global Chat" />
</ListItem>
<Divider />
{conversations && (
<React.Fragment>
// 87 {conversations.map((c) => ( // error on this line
<ListItem
className={classes.listItem}
key={c._id}
button
onClick={() => {
props.setUser(handleRecipient(c.recipientObj));
props.setScope(handleRecipient(c.recipientObj).name);
}}
>
<ListItemAvatar>
<Avatar>
{commonUtilites.getInitialsFromName(
handleRecipient(c.recipientObj).name
)}
</Avatar>
</ListItemAvatar>
<ListItemText
primary={handleRecipient(c.recipientObj).name}
secondary={<React.Fragment>{c.lastMessage}</React.Fragment>}
/>
</ListItem>
))}
</React.Fragment>
)}
</List>
);
};```
I think you need to set data which you are getting in getConversations.
setConversations(res.data)
This is my scenario
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} button className={classes.mainTagItem}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
when i click on my ListItem ( that becomes selected ) i want the element <div className={classes.mainTagCircle}> has an active class
For Example:
<div classes={{ root: !!listItemSelected ? classes.mainTagCircleActive : classes.mainTagCircle, }}></div>
I have already a method onClick in my ListItem, how can i apply this logic?
given that you have a mainTag state you could compare with your element tagId to define which class to select. If it's the same as your state then active class wil be returned:
<div className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}>
</div>
Solution with a library
You could try with this library clsx. Then do something like this:
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className=
{clsx([classes.mainTagItem, mainTag === mainTagItem.tag.tagId ? :
'activeClass': 'defaultClass' ])}>
<div className={classes.mainTagCircle}></div>
<ListItemText
primary={mainTagItem.tag.name}
/>
</ListItem>
)
})}
</List>
)
}
Solution without libraries
function Component() {
const [mainTag, setMainTag] = useState(null);
return (
<List>
{mainTags.map((mainTagItem) => {
return (
<ListItem onClick={() => { setMainTag(mainTagItem.tag.tagId) }} className={
mainTag === mainTagItem.tag.tagId
? classes.mainTagCircleActive
: classes.mainTagCircle}
/>
</ListItem>
)
})}
</List>
)
}
so i am trying to create a list, whereby a list is displaying many items, and two of the items are expandable, and expanding them shows more items(subitems). I have managed to display the list, and the sub-lists, however, clicking either one of the expandable items, expands both items and not just the single one i clicked.
So firstly, i have seperated the listItems as a data structure where i can retrieve them. This is the list Items:
// Skills list in the About Page
export const listItems = [
{
listText: 'Html',
listIcon: <SendIcon/>
},
{
listText: 'CSS',
listIcon: <DraftsIcon />
},
{
listText: 'JavaScript',
listIcon: <InboxIcon />
},
{
listText: 'React',
listIcon: <SendIcon/>,
expan: true,
// SubItems
firstText:'React-Router',
secondText:'React-Navigation',
thirdText:'React-Native',
},
{
listText: 'Database',
listIcon: <DraftsIcon />,
expan: true,
// SubItems
firstText:'FireBase',
secondText:'SQL',
},
{
listText: 'ReactJs',
listIcon: <DraftsIcon />
},
{
listText: 'ReactJs',
listIcon: <DraftsIcon />
}]
This is the code where i am trying to implement the list:
import React, { Fragment } from 'react'
import Styles from './Styles'
// ListItems
import List from '#material-ui/core/List';
import ListItem from '#material-ui/core/ListItem';
import ListItemIcon from '#material-ui/core/ListItemIcon';
import ListItemText from '#material-ui/core/ListItemText';
import ExpandLess from '#material-ui/icons/ExpandLess';
import ExpandMore from '#material-ui/icons/ExpandMore';
import StarBorder from '#material-ui/icons/StarBorder';
import Collapse from '#material-ui/core/Collapse';
import {listItems} from './ListItems'
const AboutSkills = () => {
const classes = Styles()
const [open, setOpen] = React.useState(false);
const handleClick = () => {
setOpen(!open);
};
const outputList = () => (
<>
<List
style={{color:'white'}}
component="nav"
className={classes.root}
>
{ //Map through the ListItems and...
listItems.map((item, index) => {
// if expandable items exist, expand them
if(item.expan)
{
{/* Use fragment instad of <></> because key attribute is required */}
return <Fragment key={index}>
<ListItem button onClick={handleClick} className={classes.aboutList}>
<ListItemIcon className={classes.aboutIcon}>
{item.listIcon}
</ListItemIcon>
<ListItemText primary={item.listText} />
{open ? <ExpandLess /> : <ExpandMore />}
</ListItem>
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
<ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText classes={{primary:classes.expanText}}
primary={item.firstText} />
</ListItem>
{/* {console.log(item.secondText)} */}
{ item.secondText //if seconditem exists...
? <ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText classes=
{{primary:classes.expanText}} primary={item.secondText} />
</ListItem>
: console.log('No Third Item')}
{ item.thirdText //if seconditem exists...
? <ListItem button className={classes.nested}>
<ListItemIcon>
<StarBorder />
</ListItemIcon>
<ListItemText classes=
{{primary:classes.expanText}} primary={item.thirdText} />
</ListItem>
: console.log('No Third Item')}
</List>
</Collapse>
</Fragment>
} else {
return <ListItem button className={classes.aboutList} key={index}>
<ListItemIcon className={classes.aboutIcon}>
{item.listIcon}
</ListItemIcon>
<ListItemText classes={{primary:classes.unExpanText}} primary=
{item.listText} />
</ListItem>
}
})}
</List>
</>
)
return (
outputList()
)
}
export default AboutSkills
My answer in this thread is pretty much what you are searching for.
Just adapt the code according to your situation.
I don't understand why the following error appears.
I'm trying to render {links}, {collapse} in a return function, but it doesn't work.
links and collapse is about opening sub menus.
Thank you for your help.
The error message:
Objects are not valid as a React child (found: object with keys
{$$typeof, type, compare, displayName, muiName}). If you meant to
render a collection of children, use an array instead.
-- Sidebar.js
/*eslint-disable*/
import React from "react";
import classNames from "classnames";
import PropTypes from "prop-types";
import { NavLink } from "react-router-dom";
// #material-ui/core components
import { makeStyles } from "#material-ui/core/styles";
import {
Drawer,
Hidden,
List,
ListItem,
ListItemText,
Icon,
Collapse
} from "#material-ui/core";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import ListItemLink from "./ListItemLink";
// core components
import AdminNavbarLinks from "components/Navbars/AdminNavbarLinks.js";
import RTLNavbarLinks from "components/Navbars/RTLNavbarLinks.js";
import styles from "assets/jss/material-dashboard-react/components/sidebarStyle.js";
const useStyles = makeStyles(styles);
export default function Sidebar(props) {
const classes = useStyles();
// verifies if routeName is the one active (in browser input)
function activeRoute(routeName) {
return window.location.href.indexOf(routeName) > -1 ? true : false;
}
const { color, logo, image, logoText, routes } = props;
const [open, setOpen] = React.useState(true);
const handleClick = () => {
setOpen(!open);
};
var links = (
<div>
{routes.map((prop, key) => {
return (
<div>
{prop.submenu.length > 0 ? (
<ListItemLink
to={prop.layout + prop.path}
key={prop.id}
menuText={prop.name}
onClick={handleClick}
subOpen={open}
icon={prop.icon}
/>
) : (
<ListItemLink
to={prop.layout + prop.path}
key={prop.id}
menuText={prop.name}
icon={prop.icon}
/>
)}
</div>
);
})}
</div>
);
var collapse = (
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{routes.map((prop, key) => {
{
prop.submenu.map((sub, index) => {
return (
<ListItemLink
key={sub.id}
to={sub.path}
menuText={sub.name}
icon={sub.icon}
className={classes.nested}
/>
);
});
}
})}
</List>
</Collapse>
);
var brand = (
<div className={classes.logo}>
<a
className={classNames(classes.logoLink, {
[classes.logoLinkRTL]: props.rtlActive
})}
target="_blank"
>
<div className={classes.logoImage}>
<img src={logo} alt="logo" className={classes.img} />
</div>
{logoText}
</a>
</div>
);
return (
<div>
<Hidden mdUp implementation="css">
<Drawer
variant="temporary"
anchor={props.rtlActive ? "left" : "right"}
open={props.open}
classes={{
paper: classNames(classes.drawerPaper, {
[classes.drawerPaperRTL]: props.rtlActive
})
}}
onClose={props.handleDrawerToggle}
ModalProps={{
keepMounted: true // Better open performance on mobile.
}}
>
{brand}
<div className={classes.sidebarWrapper}>
{props.rtlActive ? <RTLNavbarLinks /> : <AdminNavbarLinks />}
<List component="nav" className={classes.list}>
{links}
{collapse}
</List>
</div>
{image !== undefined ? (
<div
className={classes.background}
style={{ backgroundImage: "url(" + image + ")" }}
/>
) : null}
</Drawer>
</Hidden>
<Hidden smDown implementation="css">
<Drawer
anchor={props.rtlActive ? "right" : "left"}
variant="permanent"
open
classes={{
paper: classNames(classes.drawerPaper, {
[classes.drawerPaperRTL]: props.rtlActive
})
}}
>
{brand}
<div className={classes.sidebarWrapper}>
<List component="nav" className={classes.list}>
{links}
{collapse}
</List>
</div>
{image !== undefined ? (
<div
className={classes.background}
style={{ backgroundImage: "url(" + image + ")" }}
/>
) : null}
</Drawer>
</Hidden>
</div>
);
}
Sidebar.propTypes = {
rtlActive: PropTypes.bool,
handleDrawerToggle: PropTypes.func,
bgColor: PropTypes.oneOf(["purple", "blue", "green", "orange", "red"]),
logo: PropTypes.string,
image: PropTypes.string,
logoText: PropTypes.string,
routes: PropTypes.arrayOf(PropTypes.object),
open: PropTypes.bool
};
-- ListItemLink.js
import React from "react";
import { ListItem, ListItemText, ListItemIcon } from "#material-ui/core";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import { PropTypes } from "prop-types";
import { Link as RouterLink } from "react-router-dom";
function ListItemLink(props) {
const { to, menuText, icon, subOpen, ...other } = props;
return (
<ListItem button component={RouterLink} to={to} {...other}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={menuText} />
{subOpen != null ? subOpen ? <ExpandLess /> : <ExpandMore /> : null}
</ListItem>
);
}
ListItemLink.propTypes = {
subOpen: PropTypes.bool,
to: PropTypes.string.isRequired
};
export default ListItemLink;
in collapse, you have two map functions. you should return the second map function.
something like this:
const collapse = (
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{routes.map((prop, key) => {
return prop.submenu.map((sub, index) => {
return (
<ListItemLink
key={sub.id}
to={sub.path}
menuText={sub.name}
icon={sub.icon}
className={classes.nested}
/>
);
});
})}
</List>
</Collapse>
);
I solved my question as followed:
I guess I returned the wrong object...
<List
{...rest}
className={clsx(classes.root, className)}
>
{pages.map(page => (
<React.Fragment key={page.title}>
<ListItem
className={classes.item}
disableGutters
key={page.title}
onClick={page.submenu.length > 0 ? handleClick : null}
open={page.submenu.length > 0 ? open : null}
>
<Button
activeClassName={classes.active}
className={classes.button}
component={CustomRouterLink}
to={page.href}
>
<div className={classes.icon}>{page.icon}</div>
{page.title}
{page.submenu.length > 0 ? (
open ? (
<ExpandLess />
) : (
<ExpandMore />
)
) : null}
</Button>
</ListItem>
<Collapse in={open} timeout={100} unmountOnExit>
<List component="div" disablePadding>
{page.submenu.map((sub, index) => {
return (
<React.Fragment key={sub.title}>
<ListItem
className={classes.nested}
disableGutters
key={sub.title}
>
<Button
activeClassName={classes.active}
className={classes.button}
component={CustomRouterLink}
to={sub.href}
>
<div className={classes.icon}>{sub.icon}</div>
{sub.title}
</Button>
</ListItem>
</React.Fragment>
);
})}
</List>
</Collapse>
</React.Fragment>
))}
</List>