How to access outer variable inside of Array[..]? - javascript

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

Getting Undefined while accessing the values of data object inside map in Next.js

// 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;

React is duplicating my object value giving me a Warning: Each child in a list should have a unique "key" prop

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)

How to change menu on button click?

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>
);

ReactJs useState Array Map

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.

React 16 counter value inside setState methods gets propagated in next list items

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>

Categories