I'm trying to build a sidebar navigation menu and thought I'd take advantage of the new State hook in React. I've read the docs but can't seem to find an example similar to what I need, which is quite simply to toggle a CSS class on click which will in turn open and close my menu.
Here's what I've tried:
const SidebarMenuItem = ({ component }) => {
const [ menuActive, setMenuState ] = useState(false);
return (
<li className="p-sidebar-menu-item">
menuActive:
{ menuActive }
<button className="p-sidebar-menu-item__link" onClick={() => setMenuState(!menuActive)}>{ component.component }</button>
{ component.children && (
<ul className="p-sidebar-menu">
<li><a href={`/${component.slug}`}>Overview</a></li>
{ component.children.map((subPage, key) => (
<li key={ key }>
<a href={`/${subPage.slug}`}>{ subPage.name }</a>
</li>
))}
</ul>
)}
</li>
)
}
export default SidebarMenuItem;
Any ideas where I'm going wrong?
Thanks
Just make the className dynamic, so instead of setting
<li className="p-sidebar-menu-item">
transform it in a template literal
<li className={`p-sidebar-menu-item`}>
and then add your class conditionally (the "yellow" class in my example)
<li className={`p-sidebar-menu-item ${menuActive ? "yellow" : ""}`}>
Take a look at this CodeSandbox: here I've just added your component and changed the way the className attribute is generated.
If you want to avoid the ternary operator you could use the classnames module and then update your code to
import c from "classnames";
...
...
...
<li className={c("p-sidebar-menu-item", {yellow: menuActive})}>
Another clean solution can be to generate the className string in advance, for example
let classes = "p-sidebar-menu-item";
if(menuActive) {
classes += " yellow";
}
<li className={classes}>
Let me know if you need some more help 😉
I think you just need
const [ menuActive, setMenuState ] = useState(false);
change the name of setState to setMenuState in your code also
Don't forget to use the prevState or you can have a bug.
<button
className="p-sidebar-menu-item__link"
onClick={() => setMenuState((prevMenuActive) => !prevMenuActive)}>
{component.component}
</button>
Related
I'm having a headache trying to understand why my onClick on a <li> doesn't trigger
I'm using react
I'm posting here the snippet of code:
This is the list creation:
<div className="countryList">
<ul>
{_adminCodes.map((item) => (
<li
style={{ cursor: "pointer" }}
onClick={handleListCountryClick}
className="countryLi">{item.name}
</li>
))}
</ul>
</div>
And this is the function handleListCountryClick:
const handleListCountryClick = () => {console.log("I'm a country!")}
Any advice would be appreciated!
EDIT:
I've tryied:
onClick={handleListCountryClick()}
onClick={() => handleListCountryClick()}
EDIT 2: This component (map) is included in a father component (graphs), if I try to trigger the onClick in the map component it runs the function, instead if I try to trigger the onClick in the father component (graphs) it doesn't work
Here's a stackblitz demo
Try calling the function with (), by using handleListCountryClick your are referencing the function instead of executing it.
onClick={() => {handleListCountryClick()}}
Dang ok are you using a class component then try:
onClick={() => {this.handleListCountryClick()}}
Sorry I tried I would need to see more code to help further.
Test this:
onClick={() => {console.log('I worked!!')}}
if that works then there is a reference error to the function.
You can try by updating your code to "onClick={() => handleListCountryClick}"
Edit:
This is what I have tried in codeSandbox and getting below results:
class Country extends React.Component {
render() {
const adminCodes = ["India", "USA", "UK", "UAE"];
return (
<div>
<ul>
{adminCodes.map((item) => (
<li
key={item}
style={{ cursor: "pointer" }}
onClick={() => handleListCountryClick(item)}
>
{item}
</li>
))}
</ul>
</div>
);
}
}
const handleListCountryClick = (item) => {
console.log(`I'm a ${item}!`);
};
export default Country;
Results (on click of listitems in same order):
I'm a India!
I'm a USA!
I'm a UK!
I'm a UAE!
usually the onClick is written onClick={() => onClickFunction()} SO the fact you tried these solutions makes me think there's a problem somewhere else. Can you please edit the question and write more of the component code so we can understand better? Thank You.
I know there is various solutions for this question already on SO.But I can't make it work.
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
<ul>
<li>className="nav-item "><a href="#!"
className={isActive ? 'm-active nav-link': "nav-link"}
onClick={toggleClass}
data-toggle="tab"
id=1>POSTS</a>
</li>
<li>........className={isActive ? 'm-active nav-link': "nav-link"}
onClick={toggleClass}.............................</li>
<li>........className={isActive ? 'm-active nav-link': "nav-link"}
onClick={toggleClass}.............................</li>
<li>........className={isActive ? 'm-active nav-link': "nav-link"}
onClick={toggleClass}.............................</li>
</ul>
So,right now when I click any li item ,it selects and add other li items to m-active className
What this looks like you can see here.enter image description here
This is happening because it doesn't know which li to select .so, I thought to add id=1 , id=2, id=3and id =4 in all li tags and then pass that value with fuction and only add m-active class only to that li tag which has same id but I don't know how can I target only that li which have same id passed by the function
const toggleClass = (id) => {
setActive(!isActive);
};
<li>className="nav-item "><a href="#!"
className={isActive ? 'm-active nav-link': "nav-link"}
onClick={() => toggleClass(1)}
data-toggle="tab"
id=1>POSTS</a>
Please see if you can help me with this or if you have other idea to solve this problem
You can track the active Li id and based on that add the active class.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const values = [
{ id: 1, text: "LI-1" },
{ id: 2, text: "LI-2" },
{ id: 3, text: "LI-3" },
{ id: 4, text: "LI-4" }
];
const [activeId, setActiveId] = useState();
return (
<div className="App">
<ul>
{values.map((val) => (
<li onClick={() => setActiveId(val.id)}>
{val.text} -- {activeId === val.id ? "Active" : "Inactive"}
</li>
))}
</ul>
</div>
);
}
Working Demo - https://codesandbox.io/s/naughty-sinoussi-bmy4p?file=/src/App.js:0-543
As each <li /> element seems to be similar, you might want to consider using .map to populate your <ul /> to simplify the process here. Something like the following:
const links = ['link1', 'link2', 'link3'];
const [active, setActive] = useState(null);
<ul>
{links.map((link) => (
<li className="nav-item">
<a
href={`#${link}`}
className={`nav-link ${active == link && 'm-active'}`}
onClick={() => setActive(link)}
>{link}</a>
</li>
))}
</ul>
All this code does is map your list of links to a list of elements that will be rendered on the page, each one has its onClick handler setting the active state var to the corresponding value.
You can set more properties about each link if you use an object as each element of the links list rather than a string; I just figured this would be the simplest way to implement the functionality you're looking for.
I see you already got a solution. That's great. I will add this to the solutions already provided.
As you want the className "active" to be applied on the element clicked, you would likely want the first item by default already selected or marked "active".
You do this by just setting the initial state to 1:
const [active, setActive] = React.useState(1);
I've got a mobile nav menu with a couple dropdown menus:
/* nav structure */
/about
/portfolio
/services
/service-1
/service-2
contact
etc..
I'm creating this menu dynamically with a loop in my render function.
I've set up a state variable to assign a class to the active dropdown menu. I'm using an onClick event/attribute on the trigger element (a small arrow image) to apply an active class to the respective dropdown menu. Kinda...
const myNavMenu = () => {
const [isSubMenuOpen, toggleSubMenu] = useState(false)
return (
<nav id="mainNav">
<ul>
{navItems.items.map((item, i) => (
<li key={i} className={
(item.child_items) !== null ? 'nav-item has-child-items' : 'nav-item'}>
<Link to={item.slug}>{item.title}</Link>
{(item.child_items === null) ? null :
<>
<img className="arrow down" src={arrowDownImg} onClick={() => toggleSubMenu(!isSubMenuOpen)} />
{printSubMenus(item)}
</>
}
</li>
))}
</ul>
</nav>
)
}
/**
* Prints nav item children two levels deep
* #param {Obj} item a navigation item that has sub items
*/
function printSubMenus(item) {
return (
<ul className={(isSubMenuOpen.current) ? "sub-menu sub-menu-open" : "sub-menu"}>
{item.child_items.map( (childItem, i) => (
<li className="nav-item" key={i}>
<Link to={childItem.slug}>{childItem.title}</Link>
{(childItem.child_items === null) ? null :
<>
<ul className="sub-sub-menu">
{childItem.child_items.map( (subItem, i) => (
<li className="sub-nav-item" key={i}>
<img className="arrow right" src={arrowRight} alt={''} />
<Link to={subItem.slug}>{subItem.title}</Link>
</li>
))}
</ul>
</>
}
</li>
))}
</ul>
)
}
}
*<Link> is a Gatsby helper component that replaces <a> tags.
The issue is that when I click my trigger, the active class is being applied to both (all) sub-menus.
I need to insert some sort of index (or Ref) on each trigger and connect it to their respective dropdowns but I'm not quite sure how to implement this.
I was reading up on useRef() for use inside of function components. I believe that's the tool I need but I'm not sure how to implement it in a loop scenario. I haven't been able to find a good example online yet.
Thanks,
p.s. my functions and loops are pretty convoluted. Very open to refactoring suggestions!
Move sub-menu to a new react component and put all the related logic in there (applying class included).
I have a React app and I am using the hooks useState and useCallback to control navigation between pages.
That part works, but I want to highlight the current page link.
So added a new state element called 'chosen' that is set with 'setChosen'.
const [chosen, setChosen] = useState();
const [showGameViewer, setGamerViewer] = useState(false);
const [showGameCreator, setGameCreator] = useState(false);
Then in my useCallback, I use setChosen:
const handleLinkMenuClick = useCallback(e => { setGamerViewer(false) || (setGameCreator(true) && setChosen(true))});
My links look like this, I am trying to append the CSS class called "current" to the className:
<li className="nav-item " {chosen && "current"}>
<a className="nav-link" href="#" onClick={handleLinkMenuClick}>Show Game Creator</a>
</li>
And my css:
.chosen {
background: yellow;
}
But I am getting an error for the above code {chosen && "current"} that says:
const chosen is undefined. Parsing Error: Unexpected token
I'm not sure why it's saying that. I have 'chosen' defined above. So it should know.
Does anyone see anything wrong?
You can render it conditionally
<li className={`nav-item ${chosen ? 'current' : ''}`}>
<a className="nav-link" href="#" onClick={handleLinkMenuClick}>Show Game Creator</a>
</li>
And I think you want to add current CSS,so you should write it as
.current {
background: yellow;
}
I have a React component that list out all users and their point rankings. I want to specific which row it the currentUser. See component:
const RankingsList = ({rankings, currentUserId}) => {
return (
<ul className="list-group">
{rankings.map(r =>
<li className="list-group-item" key={r.user_id}>
<p key={ranking.user_id}>{r.display_name} - {r.points}</p>
<p>!{currentUserId}!</p>
</li>
)}
</ul>
);
};
For each iteration of rankings, I have the r.user_id and the currentUserId. What I would like to do is when the r.user_id == currentUserId apply a class like active.
Should I be doing this inline or should this be done in the ranking array on the API or in some area of React like to reducer?
You can do it inline, for example:
<li
className={`list-group-item ${r.user_id == currentUserId ? 'active' : ''}`}
key={r.user_id}
>
If you think it's too verbose, you can also extract it in a function
<li className={getClassNames(r.user_id)} key={r.user_id}>
What's good with React is that's just javascript, so you can do it the way you would without JSX.
Just replace your current map by:
{rankings.map(r => {
const active = r.user_id === currentUserId ? 'active' : '';
return (
<li className={`list-group-item ${active}`} key={r.user_id}>
<p key={ranking.user_id}>{r.display_name} - {r.points}</p>
<p>!{currentUserId}!</p>
</li>
})
)}