I'm trying to do the following
I have a dropdown menu where I created state to have only one menu item active at a time and I will also use this to make the active page effect
And then I need to do this check on my jsx
I move to my menu in all my states
I need to check the following:
const { name, link, dropdownItems } = tag;
if my name exists in my visibleMenu array
and if it is true and I also need to check if my dropdownItems is true for there yes render my dropdown menu, basically i'm a little confused on how to do these checks in jsx
code:
const MenuBar = props => {
const MenuTags = [
{
name: 'home',
link: '/',
dropdownItems: {
names: ['one', 'two', 'three'],
link: ['/aa', '/b'],
},
},
{
name: 'about',
link: '../abovisibleMenuut',
dropdownItems: {
names: ['one', 'two', 'three'],
link: ['/aa', '/b'],
},
},
{ name: 'not dropdown', link: '../dashboard' },
{ name: 'not dropdown', link: '../dashboard/about' },
];
const [visibleMenu, setVisibleMenu] = useState(
MenuTags.reduce((r, e) => ((r[e.name] = false), r), {}),
),
onUpdateVisibility = item => {
const visibleMenuCopy = { ...visibleMenu };
Object.keys(visibleMenuCopy).forEach(
key => (visibleMenuCopy[key] = key === item),
);
setVisibleMenu(visibleMenuCopy);
};
console.log(visibleMenu);
return (
<>
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
</>
);
};
and this is my menu items: ( here i need jsx conditions )
const MenuItem = ({ tag, visibleMenu }) => {
const { name, link, dropdownItems } = tag;
console.log(visibleMenu);
return (
<NavLi>
<Link to={link}>{name}</Link>
</NavLi>
);
};
I don't know if this is the correct logic, but it was the only way I managed to get only one state of my array to be true at a time
to render my dropdown menu or apply a css to my active item
Hey I answered you other question didn't know you only wanted one visible menu but, I'll use the code I had yesterday, I still think you should change the way you are managing your visibleMenu.
Instead of setting the visible inside the MenuItem class you can set it on the MenuBar
const MenuBar = props => {
const menuTags = [
{ name: 'home', link: '/', dropdownItems: ['one', 'two', 'three'] },
// ... other menu tags you had.
]
const [visibleIndex, setVisibleIndex] = useState(null)
const = handleClick = index => {
if (visibleIndex === index) return setVisibleIndex(null)
return setVisibleIndex(index)
}
return (
<NavUl isOpen={props.isOpen}>
{menuTags.map((menuTag, index) => {
return (
<MenuTag
tag={menuTag}
visibility={index === visibleIndex}
onClick={() => handleClick(index)}
/>
)
})}
<li>
<FontAwesomeIcon
// Move the logic to only be in the Parent, this component shouldn't have to
// pass it's parents variables.
onClick={props.toggleOpen}
// ... the rest of what you had
/>
</li>
</NavUl >
)
}
// Handle visibility through props instead!
const MenuItem = ({ tag, visibility }) => {
const { name, link, dropdownItems } = tag;
return (
<NavLi >
<Link to={link}>{name}</Link>
// If these are true dropdown items will appear.
{visibility && dropdownItem && (
{dropdownItems.map(item => (
<ul>
<li>
<a>{item}</a>
</li>
</ul>
))}
)}
</NavLi>
);
};
Hope that helps and happy coding :)
Related
I am trying to create a simple button multi-select in React but I'm currently getting unexpected behaviour. I'd like users to be able to toggle multiple buttons and have them colourise accordingly, however the buttons seem to act a bit randomly.
I have the following class
export default function App() {
const [value, setValue] = useState([]);
const [listButtons, setListButtons] = useState([]);
const BUTTONS = [
{ id: 123, title: 'button1' },
{ id: 456, title: 'button2' },
{ id: 789, title: 'button3' },
];
const handleButton = (button) => {
if (value.includes(button)) {
setValue(value.filter((el) => el !== button));
} else {
let tmp = value;
tmp.push(button);
setValue(tmp);
}
console.log(value);
};
const buttonList = () => {
setListButtons(
BUTTONS.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={value.includes(bt.id) ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))
);
};
useEffect(() => {
buttonList();
}, [value]);
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>{listButtons}</div>
</div>
);
}
If you select all 3 buttons then select 1 more button the css will change.
I am trying to use these as buttons as toggle switches.
I have an example running #
Stackblitz
Any help is much appreciated.
Thanks
I think that what you want to achieve is way simpler:
You just need to store the current ID of the selected button.
Never store an array of JSX elements inside a state. It is not how react works. Decouple, only store the info. React component is always a consequence of a pattern / data, never a source.
You only need to store the necessary information, aka the button id.
Information that doesn't belong to the state of the component should be moved outside. In this case, BUTTONS shouldn't be inside your <App>.
Working code:
import React, { useState } from 'react';
import './style.css';
const BUTTONS = [
{ id: 123, title: 'button1', selected: false },
{ id: 456, title: 'button2', selected: false },
{ id: 789, title: 'button3', selected: false },
];
export default function App() {
const [buttons, setButtons] = useState(BUTTONS);
const handleButton = (buttonId) => {
const newButtons = buttons.map((btn) => {
if (btn.id !== buttonId) return btn;
btn.selected = !btn.selected;
return btn;
});
setButtons(newButtons);
};
return (
<div>
<h1>Hello StackBlitz!</h1>
<div>
{buttons.map((bt) => (
<button
key={bt.id}
onClick={() => handleButton(bt.id)}
className={bt.selected ? 'buttonPressed' : 'button'}
>
{bt.title}
</button>
))}
</div>
</div>
);
}
I hope it helps.
Edit: the BUTTONS array was modified to add a selected property. Now several buttons can be selected at the same time.
im making a portfolio website and have multiple different buttons with skills which contain an img and p tag. I want to show the description of each tag everytime a user clicks on the button. how can I do this? right now everytime user clicks it, all buttons show description.
const Skills = () => {
const [state, setState] = useState(false)
let skills = [
{ id: 1, desc: 'HTML5', state: false, img: htmlIcon },
{ id: 2, desc: 'CSS3', state: false, img: cssIcon },
{ etc....}
const showDesc = (id) => {
console.log(skills[id-1] = !state);
setState(!state)
}
return (
{skills.map(skill => (
<button onClick={(id) => showDesc(skill.id)}>
<img style={ state ? {display:'none'} : {display:'block'}} src={skill.img} />
<p style={ state ? {display:'block'} : {display:'none'}}>{skill.desc}</p>
</button>
))}
I recommend to manipulate element state instead of entry list. But if you really need to manipulate entry list you should add that list to your state. Then when you want to show/hide specific item, you need to find that item in state and correctly update entry list by making a copy of that list (with updated item). For example you can do it like this:
import React, { useState } from 'react';
const Skills = () => {
const [skills, setSkills] = useState([
{
id: 1,
desc: 'HTML5',
state: false,
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
state: false,
img: cssIcon, // your icon
},
]);
const showDesc = (id) => {
const newSkills = skills.map((item) => {
if (item.id === id) {
return {
...item,
state: !item.state,
};
}
return item;
});
setSkills(newSkills);
};
return (
<div>
{skills.map(({
id,
img,
state,
desc,
}) => (
<button type="button" key={id} onClick={() => showDesc(id)}>
<img alt="img" style={state ? { display: 'none' } : { display: 'block' }} src={img} />
<p style={state ? { display: 'block' } : { display: 'none' }}>{desc}</p>
</button>
))}
</div>
);
};
Instead of manipulating all list, you can try to move show/hide visibility to list item itself. Create separate component for item and separate component for rendering that items. It will help you to simplify logic and make individual component responsible for it visibility.
About list rendering you can read more here
For example you can try something like this as alternative:
import React, { useState } from 'react';
const skills = [
{
id: 1,
desc: 'HTML5',
img: htmlIcon, // your icon
},
{
id: 2,
desc: 'CSS3',
img: cssIcon, // your icon
},
];
const SkillItem = ({
img,
desc = '',
}) => {
const [visibility, setVisibility] = useState(false);
const toggleVisibility = () => {
setVisibility(!visibility);
};
const content = visibility
? <p>{desc}</p>
: <img alt="img" src={img} />;
return (
<div>
<button type="button" onClick={toggleVisibility}>
{content}
</button>
</div>
);
};
const SkillList = () => skills.map(({
id,
img,
desc,
}) => <SkillItem img={img} desc={desc} key={id} />);
I have a problem cause I'm getting the userAccess from redux. I need to identify if it is admin or not.
My problem is that its not inside a hook so I can't get the redux value.
here's my code below
Code
import { useSelector } from 'react-redux';
const role = useSelector((state) => state.role);
export default [
{
id: 1,
text: 'Leaders',
url: `/leaders`,
userAccess: true,
},
{
id: 2,
text: 'Users',
url: `/users`,
userAccess: role === 'Admin',
},
];
Code
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
return (
<div className={classes.root}>
{items.map(
(item) =>
item.userAccess && (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};
You are correct, the useSelector hook can't be used on it's own outside a React functional component or custom React hook.
I suggest you configure your exported menu items to either set userAccess to a boolean or role value, and access your redux state in the Menu component.
Menu items
export default [
{
id: 1,
text: 'Leaders',
url: `/leaders`,
userAccess: true,
},
{
id: 2,
text: 'Users',
url: `/users`,
userAccess: 'Admin',
},
];
Menu
import { useSelector } from 'react-redux';
const checkAccess = (access, role) => {
if (typeof access === 'string') {
return role === access;
};
return access;
};
const Menu = ({ items, isCollapseMenu }) => {
const classes = useStyles();
const role = useSelector((state) => state.role);
return (
<div className={classes.root}>
{items.map(
(item) =>
checkAccess(item.userAccess, role) && (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};
You need to run useSelector() in a component or in another hook. You can create a custom hook (useRoles) that returns the array of roles, and use it in the component:
const useRoles = () => {
const role = useSelector((state) => state.role);
return [{
id: 1,
text: 'Leaders',
url: `/leaders`,
userAccess: true,
},
{
id: 2,
text: 'Users',
url: `/users`,
userAccess: role === 'Admin',
},
];
};
Get the items from the useRoles hook:
const Menu = ({ isCollapseMenu }) => {
const items = useRoles();
const classes = useStyles();
return (
<div className={classes.root}>
{items.map(
(item) =>
item.userAccess && (
<MenuItem
key={item.id}
title={item.title}
icon={item.icon}
text={item.text}
shortText={item.shortText}
url={item.url}
submenu={item.submenu}
isCollapseMenu={isCollapseMenu}
/>
)
)}
</div>
);
};
I want to create something similar to an accordeon:
https://codesandbox.io/s/suspicious-golick-xrvt5?file=/src/App.js
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const items = [
{
title: "lorem1",
content: "content1"
},
{
title: "lorem2",
content: "content2"
}
];
const [hide, setHide] = useState({
title: "",
open: false
});
const hideContent = (title) => {
setHide({
title: title,
open: !hide.open
});
};
return (
<div className="App">
{items.map((i) => {
return (
<div>
<h1 onClick={() => hideContent(i.title)}>{i.title}</h1>
{hide.title === i.title && hide.open ? <p>{i.content}</p> : ""}
</div>
);
})}
</div>
);
}
Now when i click on a title its content appears, but when click on another title the first content disappear but the actual clicked does not appear. How to click on the second title and to hide the previous content and in the same time to open the actual clicked item content?
I think instead of going with open state. Go with id. You item state
const items = [
{
id: 0,
title: "lorem1",
content: "content1"
},
{
id: 1,
title: "lorem2",
content: "content2"
}
];
Pass id in hideContent:
const hideContent = (title, id) => {
setHide(
(prev) =>
({
title: title,
open: prev.open === id ? null : id
})
);
}
and condition to check inside return like this:
hide.title === i.title && hide.open === i.id ? (
<p>{i.content}</p>
) : (
""
)}
here is demo and full code: https://codesandbox.io/s/upbeat-brattain-8yfx7?file=/src/App.js
hi i have a menu with tag structure < li> < a> and some of these menu items will be dropdown
then I created a state array to store all my menu items and when one is clicked it needs to be true and all the others are false, that is, in this array only one item can be true, with that I can show my dropdown item or even even apply a css effect to my active item, but for some reason my state never changes to true when clicked:
code:
const MenuItem = ({ tag, visibleMenu }) => {
const { name, link, dropdownItems } = tag;
return (
<NavLi>
<a>{name}</a>
{visibleMenu[name] && dropdownItems ? (
<DropDown>
{dropdownItems.map(item => (
<li>
<a href={item.link}>{item.name}</a>
</li>
))}
</DropDown>
) : (
""
)}
</NavLi>
);
};
const MenuBar = props => {
const MenuTags = [
{
name: "home",
link: "/",
dropdownItems: [
{ name: "one", link: "/aa" },
{ name: "two", link: "/b/" }
]
},
{
name: "about",
link: "../abovisibleMenuut",
dropdownItems: [
{ name: "one", link: "/aa" },
{ name: "two", link: "/b/" }
]
},
{ name: "not dropdown", link: "../dashboard" },
{ name: "not dropdown", link: "../dashboard/about" }
];
const [visibleMenu, setVisibleMenu] = useState(
MenuTags.reduce((r, e) => ((r[e.name] = false), r), {})
),
onUpdateVisibility = item => {
const visibleMenuCopy = { ...visibleMenu };
Object.keys(visibleMenuCopy).forEach(
key => (visibleMenuCopy[key] = key === item)
);
setVisibleMenu(visibleMenuCopy);
};
console.log(visibleMenu);
return (
<NavUl isOpen={props.isOpen}>
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
<li>
<FontAwesomeIcon
onClick={() => props.setOpenBox(!props.isOpen)}
className="searchIcon"
rotation={90}
icon={faSearch}
size="1x"
fixedWidth
color="rgba(0, 0, 0, 0.08);"
/>
</li>
</NavUl>
);
};
example:
https://codesandbox.io/s/lucid-sammet-9qim9
I believe my problem is in my state:
const [visibleMenu, setVisibleMenu] = useState(
MenuTags.reduce((r, e) => ((r[e.name] = false), r), {}),
),
onUpdateVisibility = item => {
const visibleMenuCopy = { ...visibleMenu };
Object.keys(visibleMenuCopy).forEach(
key => (visibleMenuCopy[key] = key === item),
);
setVisibleMenu(visibleMenuCopy);
};
{MenuTags.map(item => (
<MenuItem
tag={item}
visibleMenu={visibleMenu}
onClick={() => onUpdateVisibility(item)}
/>
))}
But it was the only way I found to make only one state true at a time when it is clicked
The issue was that the event handler was on a component that didn't return any concrete HTML elements. MenuTag returns NavLI, which is a styled component, which returns an li. I'm no master of styles components, but I think in general, event handlers need to be attached to DOM elements, not abstract React components.
Also, I think you can simplify this dramatically. What about this:
const [visible, setVisible] = useState('none'),
{MenuTags.map( (item, index) => (
<MenuItem
key={index}
tag={item}
visibleMenu={visible === index ? true : false}
onClick={() => setVisible(index)}
/>
))}
So what this does is say that within this component, we keep track of which item is visible with the visible state variable, which will be the index of the visible item. The onClick handler changes this value to the index of the clicked item. Then your prop visibleMenu will check this condition, making sure the index of that MenuTag is the same as the one in state.
Edit
I changed a few things. Instead of controlling the state from within MenuItem, we just pass a few props to MenuItem, and control them inside of MenuItem's subcomponent, NavLI, which accepts event handlers
const MenuBar = props => {
const [visible, setVisible] = useState('none')
console.log(visible);
return (
<NavUl isOpen={props.isOpen}>
{MenuTags.map( (item, index) => (
<MenuItem
key={index}
index={index}
tag={item}
setVisible={setVisible}
visibleMenu={visible === index ? true : false}
/>
))}
// Inside NavLI
const MenuItem = ({ tag, visibleMenu, setVisible, index }) => {
...
return (
<NavLi onMouseEnter={ () => { console.log(index); setVisible(index) }}
onMouseLeave={() => { setVisible('none')}} >
...
I changed the handlers to onMouseEnter and onMouseLeave. onClick will work, but you'd need a global event handler for the page to setVisible back to 'none', so that when you click anywhere else on the page, the tabs close. There's more nuance there than that, but my example should work.
Working Codesandbox