I am working with React and trying to build a multi-level and dynamic navmenu with submenu and sidemenu.
This is my nav.js component.
import React from "react";
import SubMenu from "./submenu";
function Navbar () {
return (
<>
<ul>
<li>
<span> Home </span>
</li>
<li>
<span> Category </span>
<SubMenu subtitle="Category" />
</li>
<li>
<span> Pages </span>
<SubMenu subtitle="Pages" />
</li>
</ul>
</>
);
}
export default Navbar;
And this is my submenu.js component.
import React from "react";
function SubMenu (props) {
const navtitle = props.subtitle;
const allSubMenuItem = {
Category: [
{
id: 1,
title: "Grocery",
},
{
id: 2,
title: "Fashion",
},
{
id: 3,
title: "Electronics",
}
]
Pages: [
{
id: 1,
title: "Abouts Us",
},
{
id: 2,
title: "Contact us",
},
{
id: 3,
title: "Term & Conditions",
}
]
}
function menuBody (index, title) {
const sid = index;
const stitle = title;
return (
<>
<li key={ sid }>
<span> { stitle } </span>
</li>
</>
);
};
return (
<>
<ul>
{
allSubMenuItem[`"${navtitle}"`].map((data, index) => (
menuBody(index, data.title)
))
}
</ul>
</>
);
}
export default SubMenu;
I am getting an error "Uncaught TypeError: allSubMenuItem[((""" + (intermediate value)) + """)] is undefined".
Here I am using props to send the nav-menu-title to the submenu components. And the submenu component get the nav-menu-title from the parent component. I verified that using alert(`"${navtitle}"`) The problem is I can't access the variable navtitle at this line of my code allSubMenuItem[`"${navtitle}"`].map((data, index) =>
How can I access the props values inside of allSubMenuItem[].map() ?
You need to remove double qoutation marks while dynamically getting the object key
<ul>
{
allSubMenuItem[`${navtitle}`].map((data, index) => (
menuBody(index, data.title)
))
}
</ul>
Here is your complete Navbar.
import React from "react";
import SubMenu from "./submenu";
function Navbar() {
return (
<>
<ul>
<li>
<span> Home </span>
</li>
<li>
<span> Category </span>
<SubMenu subtitle="Category" />
</li>
<li>
<span> Pages </span>
<SubMenu subtitle="Pages" />
</li>
</ul>
</>
);
}
export default Navbar;
function SubMenu(props) {
const navtitle = props.subtitle;
const allSubMenuItem = {
Category: [
{
id: 1,
title: "Grocery"
},
{
id: 2,
title: "Fashion"
},
{
id: 3,
title: "Electronics"
}
],
Pages: [
{
id: 1,
title: "Abouts Us"
},
{
id: 2,
title: "Contact us"
},
{
id: 3,
title: "Term & Conditions"
}
]
};
function menuBody(index, title) {
const sid = index;
const stitle = title;
return (
<>
<li key={sid}>
<span> {stitle} </span>
</li>
</>
);
}
return (
<>
<ul>
{allSubMenuItem[`${navtitle}`].map((data, index) =>
menuBody(index, data.title)
)}
</ul>
</>
);
}
allSubMenuItem is an object. Hence if you want to map through it, use need to use Object.keys(myObject).map()
So, your submenu.js should probably look like this
import React from "react";
function SubMenu (props) {
const navtitle = props.subtitle;
const allSubMenuItem = {
Category: [
{
id: 1,
title: "Grocery",
},
{
id: 2,
title: "Fashion",
},
{
id: 3,
title: "Electronics",
}
]
Pages: [
{
id: 1,
title: "Abouts Us",
},
{
id: 2,
title: "Contact us",
},
{
id: 3,
title: "Term & Conditions",
}
]
}
function menuBody (index, title) {
const sid = index;
const stitle = title;
return (
<>
<li key={ sid }>
<span> { stitle } </span>
</li>
</>
);
};
return (
<>
<ul>
{
Object.keys(allSubMenuItem).map((key)=>{
allSubMenuItem[key].map((data, index) => (
menuBody(index, data.title)
))
})
}
</ul>
</>
);
}
export default SubMenu;
Related
// while accessing the object values from data, I'm getting undefined in map
// ../data/section1
const data = [{
id: 1,
image: './images/homepage/xbox-games.png',
text: 'Buy Xbox games and consoles',
}, {
id: 2,
image: './images/homepage/shop_surface_devices.webp',
text: 'Shop surface devices',
}, {
id: 3,
image: './images/homepage/choose_your_ms_365.png',
text: 'Choose your Microsoft 365',
}, {
id: 4,
image: './images/homepage/shop_windows_10.png',
text: 'Shop Windows 10',
}]
export default data;
// the actual component
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
})}
</div>
</>
)
}
export default Section1;
return JSX from the map
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => {
return (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
)
})}
</div>
</>
)
}
export default Section1;
I had the same problem, then tried the first bracket instead of the second, and it resolved the problem
import data from "../data/section1";
const Section1 = () => {
return (
<>
<div class="mx-20">
{data.map((vals) => (
<div class="">
<img src={vals.image}/>
<p>{vals.text}</p>
</div>
))}
</div>
</>
)
}
export default Section1;
Im trying to make a navigation bar for a website and it's giving me the "Warning: Each child in a list should have a unique "key" prop." inside my props.dropList.map
I have two files:
NavigationItems.js -> where I render my navigation bar
const NavigationItems = () => {
const projectDropdown = [
{ id: 0, value: "architecture" },
{ id: 1, value: "land" },
{ id: 2, value: "design" },
{ id: 3, value: "list" },
];
const officeDropdown = [
{ id: 4, value: "contact" },
{ id: 5, value: "team" },
];
return (
<div>
<ul className={styles.NavigationItems}>
<NavigationItem
link={`/projects`}
name="projects"
dropList={projectDropdown}
/>
<NavigationItem link={`/news`} name="news" exact />
<NavigationItem
link={`/office`}
name="office"
dropList={officeDropdown}
/>
</ul>
</div>
);
};
export default NavigationItems;
NavigationItem.js -> where I use the map function
const NavigationItem = (props) => {
let i = 0;
return (
<li className={styles.NavigationItem}>
<NavLink to={props.link} activeClassName={styles.active}>
{props.name}
</NavLink>
{props.dropList && (
<div className={styles.DropdownItems}>
<ul className={styles.DropdownItem}>
{props.dropList.map((drop) => {
console.log("i " + i);
console.log("id " + drop.id);
console.log("value " + drop.value);
i++;
return (
<li key={drop.id}>
<NavLink
exact
to={`${props.link}/${drop.value}`}
activeClassName={styles.active}
>
{drop.value}
</NavLink>
</li>
);
})}
</ul>
</div>
)}
</li>
);
};
export default NavigationItem;
So what happens is that the code loops twice duplicating the key values. It should be looping only once. I don't know why it loops twice, I'm only mapping my values once. For reference
this is what my console shows when I click my links
So your problem doesn't occure in either of the components you provided, but in your "Land" component. (Check the render method of Land)
I am trying to make an authentication based menus in react app.
Menu Data:
const menuItems = {
primaryMenus: [
{ title: 'Messages' },
{ title: 'Register' },
{
subMenus: {
title: 'Account',
menus: [{ title: "Profile" }, { title: "Change Password"}],
},
},
{ title: 'Help' }
],
};
From the above data, I need to build up the menu structure.
The code that I have tried so far
const menuItems = {
primaryMenus: [
{ title: 'Messages' },
{ title: 'Register' },
{
subMenus: {
title: 'Account',
menus: [{ title: "Profile" }, { title: "Change Password"}],
},
},
{ title: 'Help' }
],
};
function App() {
const [ isAuthenticated, setAuthenticated ] = React.useState(false);
return(
<div>
<button onClick={() => {setAuthenticated(!isAuthenticated)}}> {isAuthenticated ? 'Logout' : 'Login'} </button>
<ul className="menu">
{menuItems.primaryMenus.map((menu, i) => {
return (
!menu.subMenus ?
<li key={i}> {menu.title} </li>
:
<li key={i}>
{menu.subMenus.title}
<ul>
{ menu.subMenus.menus.map((submenu, j) => {
return <li key={j}> {submenu.title} </li>
}) }
</ul>
</li>
)
})}
</ul>
<h1> The menu Messages and Help will be there for both logged in user and logged out user </h1>
<br />
<h1> Whereas the Register menu will be available only if user is logged out </h1>
<br />
<h1> My Account menu and its submenus will be available only if user is logged in </h1>
</div>
)
}
ReactDOM.render(<App />, document.querySelector('#app'));
<script src="https://unpkg.com/react#16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16.7.0-alpha.0/umd/react-dom.development.js"></script>
<div id="app"></div>
Requirement:
Menu structure for logged in user:
- Messages
- Account
- Profile
- Change Password
- Help
Menu structure for logged out user:
- Messages
- Register
- Help
I can modify the provided json structure (menuItems) as well..
Kindly help me to achieve the above result.
I am new to react, so if anyone could provide me a solution in pure react way of authentication then it would be much more helpful for me..
I would recommend you changed the structure of the array menuItems. For example like this:
const menuItems = [
{ title: 'Messages', whenLoggedIn: true, whenLoggedOut: true },
{ title: 'Help', whenLoggedIn: true, whenLoggedOut: true },
{ title: 'Register', whenLoggedIn: false, whenLoggedOut: true },
{
title: 'Account',
whenLoggedIn: true,
whenLoggedOut: false,
subMenuItems: [{ title: 'Profile' }, { title: 'Change password' }],
},
];
Then you can use them like this:
const keepMenuItem = (menuItem, isAuthenticated) =>
(isAuthenticated && menuItem.whenLoggedIn)
|| (!isAuthenticated && menuItem.whenLoggedOut)
return (
<ul className="menu">
{menuItems.filter(menuItem => keepMenuItem(menuItem, isAuthenticated)
.map(menuItem => (
<li key={menuItem.title}>
{menuItem.title}
{!!menuItem.subMenuItems && (
<ul>
{menuItem.subMenuItems.map(subMenuItem => (
<li key={subMenuItem.title}>{subMenuItem.title}</li>
))}{' '}
</ul>
)}
</li>
))}
</ul>
);
Hello I have doubts on how I can do this in react using useState,
basically i have this menu where i need to map, i basically need a state containing all tags, and with boolean state true or false to know if the current item is active, and i will make it active by clicking on the item, and deactivate it when another item is clicked
that is, only one menu item active at a time
export const SideBarTags = [
{
name: 'Tutoriais',
link: '../tutorials',
icon: faFileAlt,
dropdownItems: null,
active: false,
},
{
name: 'Avisos',
link: '../news',
icon: faNewspaper,
dropdownItems: null,
active: false,
},
{
name: 'Serviços',
link: '../services',
icon: faMeteor,
active: false,
dropdownItems: [
{ name: 'Elo Boost', link: '/eloBost' },
{ name: 'Duo Boost', link: '/duoBoost' },
{ name: 'MD10', link: '/eloBost' },
{ name: 'Coaching', link: '/duoBoost' },
{ name: 'Vitóriais', link: '/duoBoost' },
],
},
{
name: 'Carteira',
link: '../cartcredit',
icon: faWallet,
active: false,
dropdownItems: [
{ name: 'Histórico', link: '/history' },
{ name: 'Adicionar Crédito', link: '/add' },
],
},
];
and my TSX:
const MenuTags: React.FC<Hamburguer> = ({ isOpen }) => {
const [menuTags, setMenuTags] = useState(SideBarTags.map());
return (
<DashMenu open={isOpen}>
<MenuItem /> //(this is my tag <li>
</DashMenu>
);
};
const MenuItem: React.FC = () => {
return (
<ListItem>
<ListWrap
>
<a>
<FontAwesomeIcon
className="icon-li"
icon={icon}
size={isOpen ? 'lg' : 'lg'}
fixedWidth
color="white"
/>
<span
className="li-name"
>
{name}
</span>
</a>
</ListItem>
);
};
Component logic if you wanted to map the menu items with the active item
const [menuItems, setMenuItems] = useState(SideBarTags);
const clickHandler = name => () => {
setMenuItems(items =>
items.map(item => ({
...item,
active: item.name === name
}))
);
};
...
{menuItems.map(item => (
<li
key={item.name}
className={item.active ? "active" : ""}
onClick={clickHandler(item.name)}
>
{item.name}
</li>
))}
CSS
.active {
// could be whatever style you need
color: red;
}
Super simplified version of what I did in a past project:
const MenuTags = () => {
const [selectedLink, setSelectedLink] = useState(null)
return (
<ul>
{SideBarTags.map((obj) => (
<li className={`${selectedLink === obj.name ? 'link--selected' : ''}`}>
<a
onClick={() => {
setSelectedLink(obj.name)
}}
href={obj.link}
>
{obj.name}
</a>
</li>
))}
</ul>
)
}
Use CSS to open and close the menu items, by having a class such as 'link--selected' added to an element you can just show that item.
I have achieved to increase the list items count values and cost assigned to them using reduce, setState methods e.g Tea x 2 (clicked n times, here 2) then cost will become 15 x 2 = 30; depends on the number of clicks. This is working.
When I click on first items e.g Tea x 1 = 15 then the second item twice coffee x 2.
What actually happens is the counter and cost gets added to coffee item and both items display counter as Tea x 2 also coffee x 2.
Where expected is Tea x 1 and coffee x 2 as clicked twice. So here setState or counter do not handle with multiple click values.
What am I missing here?
import React from "react";
import { Container, Row, Col } from "reactstrap";
const MorningDrinks = [
{
id: "1",
name: "Tea",
cost: 15
},
{
id: "2",
name: "Coffee",
cost: 15
},
{
id: "3",
name: "Milk",
cost: 15
}
];
const ChoclateDrinks = [
{
id: "4",
name: "Smothe",
cost: 15
},
{
id: "5",
name: "hot Chocolate",
cost: 15
}
];
class MenuCard extends React.Component {
state = {
selectedItems: [],
counter: 1
};
selectItem = item => {
if (this.state.selectedItems.includes(item)) {
this.setState(prevState => ({
selectedItems: prevState.selectedItems,
counter: ++this.state.counter
}));
} else {
this.setState(prevState => ({
selectedItems: prevState.selectedItems.concat(item)
}));
}
};
render() {
return (
<Container>
<p>
Welcome {this.props.name} !Pick your any Break-fast menu you want{" "}
</p>
<Row>
<Col xs="3">
<ul>
<h2>Morning Drinks </h2>
{MorningDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{" "}
{item.name} {item.cost}{" "}
</li>
))}
</ul>
<ul>
<h2>Chocolate Drinks </h2>
{ChoclateDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name}
{item.cost}
</li>
))}
</ul>
</Col>
<Col xs="3">
<ul>
<h2>Your orders </h2>
{this.state.selectedItems.map((item, i) => (
<li key={i}>
{item.name}
{item.cost}
{this.state.counter}
</li>
))}
</ul>
</Col>
<Col xs="3">
<ul>
<h3>Total</h3>
{this.state.selectedItems.reduce(
(acc, item) => acc + item.cost * this.state.counter,
0
)}
</ul>
</Col>
</Row>
</Container>
);
}
}
export default MenuCard;
Maintaining your implementation I made few changes to your existing code. See below:
import React from 'react';
import { Container, Row, Col } from "reactstrap";
const MorningDrinks = [
{
id: "1",
name: "Tea",
cost: 15
},
{
id: "2",
name: "Coffee",
cost: 15
},
{
id: "3",
name: "Milk",
cost: 15
}
];
const ChoclateDrinks = [
{
id: "4",
name: "Smoothie",
cost: 15
},
{
id: "5",
name: "Hot Chocolate",
cost: 15
}
];
class MenuCard extends React.Component {
state = {
selectedItems: []
};
selectItem = item => {
const { counter, selectedItems } = this.state;
const newItem = {
...item,
quantity: 1
};
// check if item already exist
const el = selectedItems.filter(el => el.id === newItem.id);
if (selectedItems.length === 0) {
this.setState({
selectedItems: [...selectedItems, newItem]
});
} else {
if (el.length) {
const newSelectedItems = selectedItems.map((item) => {
if (item.id === newItem.id) {
item.quantity++;
}
return item;
});
this.setState({
selectedItems: newSelectedItems
});
} else {
this.setState({
selectedItems: [...selectedItems, newItem]
});
}
}
};
render() {
const { counter, selectedItems } = this.state;
return (
<Container>
<p>
Welcome {this.props.name}! Pick your any Break-fast menu you want{" "}
</p>
<Row>
<Col xs="3">
<ul>
<h2>Morning Drinks </h2>
{MorningDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{" "}
{item.name} {item.cost}{" "}
</li>
))}
</ul>
<ul>
<h2>Chocolate Drinks </h2>
{ChoclateDrinks.map((item, i) => (
<li
style={{ cursor: "pointer" }}
key={i}
onClick={() => this.selectItem(item)}
>
{item.name} {item.cost}
</li>
))}
</ul>
</Col>
<Col xs="3">
<ul>
<h2>Your orders </h2>
{selectedItems.map((item, i) => (
<li key={i}>
{item.name} {item.cost} {item.quantity}
</li>
))}
</ul>
</Col>
<Col xs="3">
<ul>
<h3>Total</h3>
{selectedItems.reduce(
(acc, item) => acc + item.cost * item.quantity,
0
)}
</ul>
</Col>
</Row>
</Container>
);
}
}
export default MenuCard;
Under your current implementation: counter is a blanket variable that increments once per click anywhere in your menu.
Hence, counter only knows total clicks, not individual clicks.
I assume you want to count clicks per item.
The easiest way to do this would be within your record of each item that has been clicked.
Your problem is a lot easier to solve if you manage your cart (selectedItems) in object form, wherein each object carries an individual quantity property.
See below for a practical example.
// Drinks.
const drinks = [
{
id: "1",
name: "tea",
cost: 15
},
{
id: "2",
name: "coffee",
cost: 15
},
{
id: "3",
name: "milk",
cost: 15
},
{
id: "4",
name: "smoothie",
cost: 15
},
{
id: "5",
name: "hot chocolate",
cost: 15
}
]
// Menu.
class Menu extends React.Component {
// Constructor.
constructor(props) {
super(props) // Super Props.
this.state = {cart: {}} // Initial State.
}
// Render.
render() {
// Variables.
const { state } = this // State.
const { cart } = state // Drinks.
return (
<div>
{/* Drinks. */}
<ul>
<h2>Drinks</h2>
{drinks.map((drink, index) => (
<li style={{ cursor: "pointer" }} key={drink.id} onClick={() => this.addToCart(drink)}>
{drink.name} ${(drink.cost).toFixed(2)}
</li>
))}
</ul>
{/* Cart. */}
<ul>
<h2>Cart</h2>
{Object.keys(cart).map((key, index) => (
<li key={key}>
${(cart[key].cost).toFixed(2)}
{cart[key].name}
{cart[key].quantity}
</li>
))}
</ul>
{/* Total. */}
<ul>
<h3>Total</h3>
${Object.keys(cart).reduce((total, key) => total + (cart[key].cost * cart[key].quantity), 0).toFixed(2)}
</ul>
</div>
)
}
// Add To Cart.
addToCart = (item) => {
// Variables.
const { state } = this // State.
const { cart } = state // Selected Items.
const { id } = item
// Is In Cart?
const isInCart = cart[id] // Is In Cart.
if (isInCart) return this.setState({cart: {...cart, [id]: {...cart[id], quantity: cart[id].quantity + 1}}}) // Update Cart.
// Does Not Exist.
return this.setState({cart: {...cart, [id]: {...item, quantity: 1}}}) // Add To Cart.
}
}
// ReactDOM.render.
ReactDOM.render(<Menu/>, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>