In a react application, I am using reactstrap css framework to make the dropdowns to list the dropdown menus.
Example.Js
<Dropdown
className="d-inline-block"
onMouseOver={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen}
toggle={this.toggle}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
<br /><br />
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
Here I have made the setState to set the state for dropDownOpen in the events such as onMouseOver and onMouseLeave.
The issue is on hover of single dropdown menu, every dropdown gets opened.
Click here for Working Demo
Kindly help me to make the hover dropdown to only list the menus of hovered one and not all at a time.
Note: In my real application, these dropdown menus will be dynamic, So I cant make any hardcoded state like, dropDown1, dropDown2, dropDown3 ... etc ..
It may have any n number of dropdowns.. So please give me solution in keeping the dynamic menus into consideration.
Basically each dropdown menu needs to be in its own Dropdown composite component, with its own state and handlers. I forked and updated your demo that hopefully gives you the right idea.
<div>
<Dropdown
className="d-inline-block"
onMouseOver={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen1}
toggle={this.toggle1}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
// onMouseOver={this.onMouseEnter}
// onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen2}
toggle={this.toggle2}
>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
<br /><br />
</Dropdown>
<Dropdown
className="d-inline-block"
// onMouseOver={this.onMouseEnter}
// onMouseLeave={this.onMouseLeave}
isOpen={this.state.dropdownOpen3}
toggle={this.toggle3}
>
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
https://stackblitz.com/edit/reactstrap-v6-2dnzex?file=Example.js
Here is my solution to show the submenu of the menu or dropdown.
Feel free to ask any questions or if you have any confusion about it.
import React from 'react';
import { Box, jsx } from 'theme-ui';
import { Link } from 'gatsby';
import { H1 } from '../../components/ThemeHeader';
import { Image } from '../../components';
const CardWithCTALinks = (props) => {
const { cardWithCTALinksImage, ctaLinks, heading, bgColor } = props;
const [onCTAListHover, setOnCTAListHover] = React.useState({ status: '', indexAt: -1 });
const updateCTAListHover = (newOnCTAListHover, idx) => {
if (newOnCTAListHover !== onCTAListHover) setOnCTAListHover({ state: newOnCTAListHover, indexAt: idx });
};
const renderImageSection = (src, alt) => {
return <Image src={src} alt={alt} />;
};
const renderSubLinks = (subLinks, idx) => {
return (
<Box>
{idx === onCTAListHover.indexAt &&
subLinks.map((link) => (
<Link key={Math.random().toString(36).substring(7)} to="/">
{link.text}
</Link>
))}
</Box>
);
};
const renderLinksSection = (linksList, headingText) => {
return (
<Box>
{headingText && <H1>{headingText}</H1>}
{linksList && (
<Box>
{linksList.map((link, index) => (
<h1 onMouseEnter={() => updateCTAListHover(true, index)} onMouseLeave={() => updateCTAListHover(false, index)}>
{link.node?.title}
{link.node?.navItems.length > 0 && <>{onCTAListHover && renderSubLinks(link.node?.navItems, index)}</>}
</h1>
))}
</Box>
)}
</Box>
);
};
return (
<Box style={{ backgroundColor: bgColor }}>
{cardWithCTALinksImage && <Box>{renderImageSection(cardWithCTALinksImage?.asset._ref, 'alt')}</Box>}
{ctaLinks && heading && <Box>{renderLinksSection(ctaLinks.edges, heading)}</Box>}
</Box>
);
};
export default CardWithCTALinks;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from "react";
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem
} from "reactstrap";
export default class Example extends React.Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.onMouseEnter = this.onMouseEnter.bind(this);
this.onMouseLeave = this.onMouseLeave.bind(this);
this.state = {
dropdownOpen1: false,
dropdownOpen2: false,
dropdownOpen3: false
};
}
toggle(id) {
this.setState({[id]:!this.state[`${id}`]})
}
onMouseEnter(id) {
this.setState({ [id]: true });
}
onMouseLeave(id) {
this.setState({ [id]: false });
}
render() {
return (
<div>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen1")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen1")}
isOpen={this.state.dropdownOpen1}
toggle={()=>this.toggle("dropdownOpen1")}
>
<DropdownToggle caret>Dropdown1</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 1</DropdownItem>
<DropdownItem>Submenu 1.1</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen2")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen2")}
isOpen={this.state.dropdownOpen2}
toggle={()=>this.toggle("dropdownOpen2")}
>
<DropdownToggle caret>Dropdown2</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 2</DropdownItem>
<DropdownItem>Submenu 2.1</DropdownItem>
<DropdownItem>Submenu 2.2</DropdownItem>
</DropdownMenu>
</Dropdown>
<Dropdown
className="d-inline-block"
onMouseOver={()=>this.onMouseEnter("dropdownOpen3")}
onMouseLeave={()=>this.onMouseLeave("dropdownOpen3")}
isOpen={this.state.dropdownOpen3}
toggle={()=>this.toggle("dropdownOpen3")}
>
<br />
<br />
<DropdownToggle caret>Dropdown3</DropdownToggle>
<DropdownMenu>
<DropdownItem header>Submenu 3</DropdownItem>
<DropdownItem>Submenu 3.1</DropdownItem>
<DropdownItem>Submenu 3.2</DropdownItem>
<DropdownItem>Submenu 3.3</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
);
}
}
Try it out once !!!let me know,basically you need to pass different args for each dropdown ,so that your method can differentiate what to open or close.
Hi #TestUser and Drew Reese I have reduces some line using arrow functions: https://stackblitz.com/edit/reactstrap-dropdown?file=Example.js and if you need this for n dropdown then you can create single drop-down and using props you can create multiple dropdown for this (you can use map for repeating for this) and can use this multiple places. that is the beauty of react.
So basically you were using same state variable for all three dropdowns.
To fix this you need to maintain three different state variables, since you want to have dynamic way you can follow the below approach
Also for reusable approach you can make the Dropdown as a separate component.
You can add more logic if you want, this is the simple way to solve the problem
App.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import { Container } from "reactstrap";
import CustomDropDown from "./CustomDropdown";
import "bootstrap/dist/css/bootstrap.min.css";
import "./styles.css";
const dropdownConfig = [
{
customKey: 1,
options: [
{ title: "Submenu 1", header: true },
{ title: "Submenu 1.1", header: false }
],
name: "dropdownOpen1"
},
{
customKey: 2,
options: [
{ title: "Submenu 2", header: true },
{ title: "Submenu 2.1", header: false }
],
name: "dropdownOpen2"
},
{
customKey: 3,
options: [
{ title: "Submenu 3", header: true },
{ title: "Submenu 3.1", header: false }
],
name: "dropdownOpen3"
}
];
function App() {
const [keysForDropdown, setKeysForDropdown] = useState({});
useEffect(() => {
const keys = dropdownConfig.map(dropdown => dropdown.name);
const object = keys.reduce((acc, curr) => {
acc[curr] = false;
return acc;
}, {});
setKeysForDropdown({ ...object });
}, []);
const _handleToggle = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
const _handleMouseEnter = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
const _handleMouseLeave = e => {
setKeysForDropdown({
...keysForDropdown,
[e.target.name]: !keysForDropdown[e.target.name]
});
};
return (
<div className="App">
<Container>
{keysForDropdown &&
dropdownConfig.map(dropdown => (
<CustomDropDown
{...dropdown}
key={dropdown.customKey}
stateKeys={keysForDropdown}
handleToggle={_handleToggle}
handleMouseEnter={_handleMouseEnter}
handleMouseLeave={_handleMouseLeave}
/>
))}
</Container>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
CustomDropdown.js
import React from "react";
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem
} from "reactstrap";
const CustomDropDown = props => {
const {
handleMouseEnter,
handleMouseLeave,
handleToggle,
options,
name,
stateKeys
} = props;
return (
<div className="dropdown-container">
<Dropdown
className="d-inline-block"
onMouseOver={handleMouseEnter}
onMouseLeave={handleMouseLeave}
isOpen={stateKeys[name]}
toggle={handleToggle}
>
<DropdownToggle name={name} caret>
Dropdown1
</DropdownToggle>
<DropdownMenu>
{options.length &&
options.map(({ header, title }) => (
<DropdownItem header={header}>{title}</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
</div>
);
};
export default CustomDropDown;
Working codesandbox
Related
I have these tabs to go to the next step. However, once clicking submits, this will go to the correct page, but this will also remove the tab. How can I fix this?
I have recreated this in codesandbox: https://codesandbox.io/s/dawn-cloud-uxfe97?file=/src/App.js
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography component="span">{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`
};
}
const Ordering = () => {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab
label="Step 1"
{...a11yProps(0)}
component={Link}
to={`/step1`}
/>
<Tab label="Step 2" {...a11yProps(1)} />
</Tabs>
</Box>
<TabPanel value={value} index={0}>
<Step1 />
</TabPanel>
<TabPanel value={value} index={1}>
<Step2 />
</TabPanel>
</div>
);
};
export default Ordering;
Step1.js
Navigating this to the step2 component does go to the next page, but this will also remove the tab
import { useNavigate } from "react-router-dom";
const Step1 = () => {
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
navigate("/Step2");
};
return (
<div>
<form onSubmit={handleSubmit}>
<input type="submit" />
</form>
</div>
);
};
export default Step1;
Step2.js
const Step2 = () => {
return <div>Step2</div>;
};
export default Step2;
In your case you're not nesting your routes properly. So, For nested route you need to define a route inside another one. Refer this page https://reactrouter.com/docs/en/v6/api#routes-and-route for more information.
Getting back to your question. You need to update your code in two place. First how the routes are defined.
<Route path="/page" element={<Page />}>
<Route path="step1" element={<Step1 />} />
<Route path="step2" element={<Step2 />} />
</Route>
Once, your routes are updated. You are linking your panels based on route. So, you need not define them again in your Page component. You can remove those component from there and just add code so that when tab is click you change your navigation bar url. Sadly Tab of mui doesn't support component property https://mui.com/api/tab/ . You have to do that manually. You can use useNavigate provided by react router dom. Your updated Page component would look like this
I have added comment // This is added. To see where I've made changes. Just in 2 places changes are required.
import React, { useState, useEffect } from "react";
import { Box, Tab, Typography, Tabs } from "#mui/material";
import PropTypes from "prop-types";
import Step1 from "./Step1";
import Step2 from "./Step2";
import { Link } from "react";
// hook is imported
import { useNavigate } from "react-router-dom";
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
<Typography component="span">{children}</Typography>
</Box>
)}
</div>
);
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired
};
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
"aria-controls": `simple-tabpanel-${index}`
};
}
const paths = ['/page/step1', '/page/step2']
const Ordering = () => {
const [value, setValue] = React.useState(0);
//This is added
const navigate = useNavigate()
const handleChange = (event, newValue) => {
setValue(newValue);
// This is added
navigate(paths[newValue])
};
return (
<div>
<Box sx={{ borderBottom: 1, borderColor: "divider" }}>
<Tabs
value={value}
onChange={handleChange}
aria-label="basic tabs example"
>
<Tab
label="Step 1"
{...a11yProps(0)}
component={Link}
to={`/step1`}
/>
<Tab label="Step 2" {...a11yProps(1)} />
</Tabs>
</Box>
{/* Removed tab panels from here */}
</div>
);
};
export default Ordering;
My main problem is that it's only rendering the last Menu Dropdown but i need different Menus (and you can see the text behind it faintly appearing). Am unsure how to pass the correct props / state to enable this
import React from 'react';
import {Button, Menu, MenuItem} from "#material-ui/core";
function Header(){
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<div>
<Button aria-controls="sessions-menu" aria-haspopup="true" onClick={handleClick}>
Sessions
</Button>
<Button aria-controls="store-menu" aria-haspopup="true" onClick={handleClick}>
Store
</Button>
<Button aria-controls= "about-menu" aria-haspopup="true" onClick={About} href="/about">
About
</Button>
<Button aria-controls="account-menu" aria-haspopup="true" onClick={handleClick}>
Account
</Button>
<Menu
id="sessions-menu"
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Book} href="/sessions/book">Book a Session</MenuItem>
<MenuItem onClick={Host} href="/sessions/host">[S] Host a session</MenuItem>
</Menu>
<Menu
id="store-menu"
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Purchase}>Purchase</MenuItem>
<MenuItem onClick={Sell}>[S] Sell</MenuItem>
</Menu>
<Menu
id="about-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
></Menu>
<Menu
id="account-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
<MenuItem onClick={Lessons}>My Lessons</MenuItem>
<MenuItem onClick={Items}>My Purchases</MenuItem>
</Menu>
</div>
);
}
export default Header;
Any help or advice for a simpler way to achieve what i want would be swell
As given in documentation anchorEl - It's used to set the position of the menu. In your code you used same anchorEl for all menu and as a result it's only rendering the last Menu Dropdown.
solution is to have anchorEl separate to each menu. for that you need to create scoped MenuButton component for each button with its menu.
You can have separate component for each button with its menu (duplication) however it better have an array of menus and render it with single component(reusability).
please check running code here https://codesandbox.io/s/header-menu-dropdown-e9e7p
I will put Header and MenuButton code here if link not work.
Header.js
import React from "react";
import MenuButton from "./MenuButton";
const Header = () => {
//added only two menu to demonstrate you can add more as per your requirement
const menu = [
{
name: "Sessions",
menuItems: [
{
name: "Book a Session",
onClick: () => {},
href:""
},
{
name: "[S] Host a session",
onClick: () => {},
href:""
}
]
},
{
name: "Store",
menuItems: [
{
name: "Purchase",
onClick: () => {}
},
{
name: "Sell",
onClick: () => {}
}
]
}
];
return menu.map((item, index) => <MenuButton key={index} menu={item} />);
};
export default Header;
MenuButton.js (button with its menu)
import React from "react";
import { Button, Menu, MenuItem } from "#material-ui/core";
const MenuButton = ({ menu }) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<>
<Button
aria-controls={`${menu.name}-menu`}
aria-haspopup="true"
onClick={handleClick}
>
{menu.name}
</Button>
<Menu
id={`${menu.name}-menu`}
anchorEl={anchorEl}
getContentAnchorEl={null}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{menu.menuItems.map((item) => (
<MenuItem onClick={item.onClick} href={item.href}>
{item.name}
</MenuItem>
))}
</Menu>
</>
);
};
export default MenuButton;
Im trying to get the value of DropdownItem in a DropDownMenu to set the value (or label name) in the DropDownToggle tag, instead of a static text that says: "Dropdown" I want the active option innerText to be displayed. (what a redundant post....)
But yes... the issue is that I'm not sure how to get the options label or value. I saw that for react native has a property that do that but Im using React.js.
I tried many solutions and one that "worked" (kind of) was to create an object with the velues but I dont know how to bind them to the options with hooks or similar, like this:
const options = [
{ value: '', label: 'All' },
{ value: 'favorite', label: 'Favorite' },
{ value: 'recent', label: 'Recent' }
];
and then render it with:
return(
<Select className="mt-4 col-md-8 col-offset-4"
options = { options }
/>
);
But again... Im not getting the idea how to bind them to my actual code... So I came with the questions... how do I get the innerText of the option that I want to be displayed when the option is active? should I save it in a state? or is there an easier way to do it?
This is my code so you can see what I mean...:
import React, {useState} from 'react';
import {
Col,
Container,
ListGroup,
ListGroupItem,
Nav,
Row,
UncontrolledDropdown,
DropdownToggle,
DropdownMenu,
DropdownItem,
} from 'reactstrap';
import {Favorite} from './Favorite';
export const ByCourse = ({projects, updateProjects}) => {
console.log('window.projects = ', projects);
const [filter, setFilter] = useState(); <-------- Here I declared my filters
const filteredProjects = !filter ? projects : projects.filter(project => project[filter]);
return (
<div className="section-component">
<h2 className="mt-2 mb-4">By Course</h2>
<Nav className="nav-tabs mb-3">
<UncontrolledDropdown setActiveFromChild>
<DropdownToggle tag="button" className="nav-link" caret>
Filter: {filter} <---------- Here I want to display the label names of the options
</DropdownToggle>
<DropdownMenu>
<DropdownItem tag="button" onClick={() => setFilter()} active>
All <--------------- this should be displayed when it is active or by default, now it display only the value of my function for filtering.
</DropdownItem>
<DropdownItem tag="button" onClick={() => setFilter('favorite')}>
Favorite <----------------- this should be displayes when active
</DropdownItem>
<DropdownItem tag="button" onClick={() => setFilter('recent')}>
Recent <----------------- this should be displayes when active
</DropdownItem>
<DropdownItem tag="button" onClick={() => setFilter('userAssigned')}>
User Assigned <----------------- this should be displayes when active
</DropdownItem>
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
<Container className={'p-0'}>
{filteredProjects.map(project => {
return (
<Row key={project.projectId} className={'h-100'}>
<Col md={12} className={'p-0'}>
<ListGroup> ........
I really apreciate any help or guidence with this reactstrap component and my facepalm issue...
After a lot of research I found the solution to my problem, it wasnt very far away from the aproch I took first. So here is a working code for getting (in this case) text of dropdown menu.
const items = [
{label: 'All', filter: project => true},
{label: 'Favorite', filter: project => project.favorite},
{label: 'Recent', filter: project => project.recent},
{label: 'User Assigned', filter: project => project.userAssigned},
];
export const ByCourse = ({projects, updateProjects}) => {
console.log('window.projects = ', projects);
const [filter, setFilter] = useState(items[0]);
const filteredProjects = !filter ? projects : projects.filter(project =>
filter.filter(project));
return (
<div className="section-component">
<h1 className="mt-2 mb-4">By Course</h1>
<Nav className="nav-tabs mb-3">
<UncontrolledDropdown setActiveFromChild>
<DropdownToggle tag="button" className="nav-link pl-0" style={{backgroundColor: 'transparent'}} caret>
Filter: {filter.label}
</DropdownToggle>
<DropdownMenu>
{items.map((item, index) => (
<DropdownItem key={index} tag="button" onClick={() => setFilter(item)} active>
{item.label}
</DropdownItem>
))}
</DropdownMenu>
</UncontrolledDropdown>
</Nav>
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>
AddCategory.js
i want to display category on this page using function mydata().
data is printing in console .i want to bind all categories in divs using mydata() function.
import { Layout, Menu, Breadcrumb, Icon, Form, Input, Button} from 'antd';
const { SubMenu } = Menu;
const { Header, Content, Sider } = Layout;
import React from "react";
import { Link, Router, Route, IndexRoute, browserHistory } from "react-router";
import { createForm } from 'rc-form';
import { Category } from "../../../imports/collections/category-db";
const FormItem = Form.Item;
function hasErrors(fieldsError) {
return Object.keys(fieldsError).some(field => fieldsError[field]);
}
class AddCategory extends React.Component {
componentDidMount() {
// To disabled submit button at the beginning.
this.props.form.validateFields();
}
handleSubmit = (e) => {
e.preventDefault();
this.props.form.validateFields((err, values) => {
if (!err) {
console.log('Received values of form: ', values);
const category_name =values.categoryName;
Meteor.subscribe('category');
Meteor.call('categoryInsert',category_name);
alert('category added cheers !');
}
});
}
state = {
collapsed: false,
};
toggle = () => {
this.setState({
collapsed: !this.state.collapsed,
});
}
mydata(){
Meteor.subscribe('category');
var categories = Category.find({}).fetch();
categories.forEach(function(cat){
console.log(cat.category_title);
});
}
render() {
const { getFieldDecorator, getFieldsError, getFieldError, isFieldTouched } = this.props.form;
// Only show error after a field is touched.
const userNameError = isFieldTouched('userName') && getFieldError('userName');
return (
<Layout>
<Sider
trigger={null}
collapsible
collapsed={this.state.collapsed}
>
<div className="logo" >
</div>
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
<Menu.Item key="1">
<Link to="/users-admin"> <Icon type="user" />
<span> USERS </span></Link>
</Menu.Item>
<Menu.Item key="2">
<Link to="/category-admin">
<Icon type="video-camera" />
<span>Category</span>
</Link>
</Menu.Item>
<Menu.Item key="3">
<Icon type="mail" />
<span>Blogs 3</span>
</Menu.Item>
<Menu.Item key="4">
<Icon type="mail" />
<span>Jobs 4</span>
</Menu.Item>
</Menu>
</Sider>
<Layout>
<Header style={{ background: '#fff', padding: 0 }}>
<Icon
className="trigger"
type={this.state.collapsed ? 'menu-unfold' : 'menu-fold'}
onClick={this.toggle}
/>
</Header>
<Content style={{ margin: '24px 16px', padding: 24, background: '#fff', minHeight: 720 }}>
<Form layout="inline" onSubmit={this.handleSubmit}>
<FormItem
validateStatus={userNameError ? 'error' : ''}
help={userNameError || ''}
>
{getFieldDecorator('categoryName', {
rules: [{ required: true, message: 'Please input your username!' }],
})(
<Input prefix={<Icon type="user" style={{ fontSize: 13 }} />} placeholder="Category tile" />
)}
</FormItem>
<FormItem>
<Button
type="primary"
htmlType="submit"
disabled={hasErrors(getFieldsError())}
>
Log in
</Button>
</FormItem>
</Form>
{this.mydata()}
</Content>
</Layout>
</Layout>
);
}
}
export default createForm()(AddCategory);
Please suggest.
You just need to return an array of JSX div entries with individual 'key' attribute from your mydata() function, such as:
mydata(){
const data = [];
Meteor.subscribe('category');
var categories = Category.find({}).fetch();
categories.forEach(function(cat, index){
console.log(cat.category_title);
data.push(<div key={`data-${index}`}>{ cat.category_title }</div>);
});
return data;
}