onClick triggering all sub menus instead of only the clicked one - reactjs

When I click on an item it should expend some sub items. This is working but if I have two, three or four etc. list items then when I click on one it expands ALL of the sub items for all the list items which is obviously not what I want. How can I fix this code to make it only open expand the one I actually clicked on?
const [sideActive, setSideActive] = useState(false);
const toggleSideActive = () => {
setSideActive(!sideActive);
};
html:
<li>
<div
onClick={toggleSideActive}
className={
sideActive
? `${styles.navList__subheading} row ${styles.row__align_v_center} ${styles.navList__subheading__open}`
: `${styles.navList__subheading} row ${styles.row__align_v_center}`
}
>
<span className={styles.navList__subheading_icon}>
<FaBriefcaseMedical />
</span>
<span className={styles.navList__subheading_title}>
insurance
</span>
</div>
<ul
className={
sideActive
? `${styles.subList}`
: `${styles.subList} ${styles.subList__hidden}`
}
>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
<li className={styles.subList__item}>medical</li>
</ul>
</li>

You can create a local state for tracking the selected id and show the content based on the state. Also, update the selected Id on click of the tab like below.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [selected, setSelected] = useState("");
const data = [
{
id: 1001,
name: "Tab - 1",
content: ["test1", "test2"]
},
{
id: 1002,
name: "Tab - 2",
content: ["test21", "test22"]
}
];
return (
<div className="App">
<ul class="parent">
{data.map((v) => (
<li onClick={() => setSelected(selected !== v.id ? v.id : "")}>
{v.name}
{selected === v.id && (
<ul class="content">
{v.content.map((val) => (
<li>{val}</li>
))}
</ul>
)}
</li>
))}
</ul>
</div>
);
}
For the below example, click on the tab to see the content.
Working code - https://codesandbox.io/s/long-leaf-cl4cy?file=/src/App.js:0-759
Let me know if you are facing any issues.

Related

React onClick event not working as expected inside loop

This is my code,
import "./styles.css";
import React from "react";
export default function App() {
const categories = [
{ name: "aaa", slug: "aaa" },
{ name: "baa", slug: "baa" }
];
const handleClick = (value) => {
console.log(value);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<div>
<ul>
{categories.map((category) => (
<li className="cat_item" key={category.slug}>
<a
value="value"
onClick={(e) => handleClick(e.currentTarget.value)}
className="dropdown-item"
>
{category.name}
<span className="text-gray-25 font-size-12 font-weight-normal"></span>
</a>
</li>
))}
</ul>
</div>
</div>
);
}
But whn I click it appear as undefined instead of value in log. Here is a fiddle of issue
This is because the target is the HTML anchor element itself and thus doesn't have a value property associated with it. In your case, I'm assuming you want to get the text within the your anchort element, try this instead.
onClick={(e) => handleClick(e.target.textContent)}
And we can use this for custom attributes,
onClick={(e) => handleClick(e.target.attributes.getNamedItem("value").value)}
Source is here
you have try to pass the value. But here no any input field that's why undefined here. You have try to pass innerText content.
onClick={(e) => handleClick(e.currentTarget.innerText)}

How can I set an element id to active when another component is scrolled into view?

So I have a sidebar and inside of it multiple buttons. When I click on the particular button, it scrolls into view a component with a certain name(I have one page with multiple components). And it works fine, components scroll into view,
but I want to set a list item id to active, according to the current component in view, so it changes color, but in the other li items active class is removed.
SideBar.jsx:
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link == val.title ? "active" : ""}
key={key}
onClick={() => {
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
So as you can see, I have a ul with list items, and when I click on each one, it scrolls a certain div into view. I also SidebarData.js file, where I store all data as an array:
SidebarData.js
export const SlidebarData = [
{
title: "Home",
link: "home"
},
{
title: "About",
link: "about"
},
{
title: "Services",
link: "services"
},
{
title: "Contact",
link: "contact"
}
];
So when a particular div is in view, I want to set a li id to active, but I can't figure out how I can tell li to do it.
you're changing id instead of class on li and id can’t be duplicate, it is not assigned correctly.
Instead of using id in your code, you might use ref.
here is a sample code to add active class to li based on elements in view.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [selectedLink, setSelectedLink] = useState("");
return (
<div className="sidebar">
<span className="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<img src={spike} />
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className={`row ${selectedLink === val.link ? "active" : ""}`}
id={val.link}
key={key}
onClick={() => {
setSelectedLink(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
</div>
);
};
There are two problems with your solution. One is that your SlidebarData has different titles and links. Thus, when using val.link === val.title in Sidebar hook, you're getting false on the condition, returning the id as blank.
On the other hand, I'm not sure how you're not getting an error with document.getElementById(val.link).scrollIntoView();. The value of val.link is going to be either Home, About, ...; however, you're setting the id of each li as either "active" or blank (""). So, document.getElementById(val.link) should return null and not the element.
EDIT: Does this solve your problem?
If you want to set the id which is active when pressed, do not use the keyword active as you'll have to change all the other id's. Create a state variable currentId (for example), and use it to set the current item you've selected.
const Sidebar = () => {
const [sideBar, setSidebar] = useState(false);
const [currentId, setCurrentId] = useState("");
return (
<div className="sidebar">
<span class="btn" onClick={() => setSidebar(!sideBar)}>
Menu
</span>
<div className="profile">
<span>Alim Budaev</span>
<span>Available for work</span>
</div>
<ul className="sidebarlist" id={sideBar ? "hidden" : ""}>
{SlidebarData.map((val, key) => {
return (
<li
className="row"
id={val.link}
key={key}
onClick={() => {
setCurrentId(val.link);
document.getElementById(val.link).scrollIntoView();
}}
>
<div>{val.title}</div>
</li>
);
})}
</ul>
<div>{currentId}</div>
</div>
);
}
Edit #2: Here is the codesandbox link, so you can have a look at the behaviour of the aforestated code.
https://codesandbox.io/s/fluent-ui-example-forked-rbd9p?file=/index.js
You'll need to use the Intersection Observer API, which will allow you to monitor and react to events which occur when tracked elements intersect a parent element (or the viewport).
Implementing this in React is non-trivial and will likely involve forwarding refs for each tracked element, however, you might find one or more community modules which already exist (e.g. react-intersection-observer).

React list item with icon on Click event

On click of list item want to show checked icon in react. Below is list of items.
Now on click of any item need to show check icon to understand this menu is clicked/active like below.
That is need to list item should be present with icon in react js. Below is current code which uis used to list items.
<div key="favorites-dropdown" className={styles.pushBtnDropDown}>
<ul key="favorites-list">
{
favorites.map((favorite, idx) => {
return (<li key={`favorite-${idx}`}>
<a
key={`favorite-a-${idx}`}
tabIndex={idx}
role="button"
onClick={() => store.setFavoriteFilters(favorite.reportName, favorite.filters)}
>
{favorite.name}
</a>
</li>);
})
}
</ul>
</div>
Can someone please help to achieve this output.
Any reference or any another example with code will be helpful.
Thanks in advance.
You can create array favoritesFilters where you will save all checked filter names and render icons with condition favoritesFilters.includes(favorite.name). Example:
import { useState } from "react";
...
const [favoritesFilters, setFavoritesFilters] = useState([]);
const handleFavorites = (favorite) => {
store.setFavoriteFilters(favorite.reportName, favorite.filters);
setFavoritesFilters(prev => {
if(prev.includes(favorite.name)) {
let newPrev = [...prev];
return newPrev.filter(item => item !== favorite.name)
}
return [...prev, favorite.name]
})
}
...
<div key="favorites-dropdown" className={styles.pushBtnDropDown}>
<ul key="favorites-list">
{
favorites.map((favorite, idx) => {
return (<li key={`favorite-${idx}`}>
//your icon
{favoritesFilters.includes(favorite.name) && <img src="your_icon_path" />}
<a
key={`favorite-a-${idx}`}
tabIndex={idx}
role="button"
onClick={() => handleFavorites(favorite)}
>
{favorite.name}
</a>
</li>);
})
}
</ul>
</div>

React dropdown function triggers all dropdowns instead of selected menu

So I'm trying to figure out how to only trigger the dropdown for the selected menu. Right now if I click on any of my menu items, it triggers every single dropdown.
I currently have a simple function that sets the state from false to true
const showSubnav = () => setSubnav(!subnav);
I attempted to use useRef() but for some reason the ref.current kept showing the wrong element that I clicked on.
Here is my current dropdown code
{SidebarData.map((item, index) => {
return (
<>
<li
ref={ref}
// Here's the function that checks if there's a sub menu, then it triggers
showSubnav
onClick={item.subNav && showSubnav}
key={index}
className={item.cName}
>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{subnav ? (
<>
{item.subNav &&
item.subNav.map((item, index) => {
return (
<div key={index} className='sub-nav-container'>
<Link to={item.path} className={item.cName}>
{item.icon}
<span>{item.title}</span>
</Link>
</div>
);
})}
</>
) : null}
</>
);
})}
So the issue is that any li with a sub menu will display if I click on it using my code
onClick={item.subNav && showSubnav}
I need a function or way to check for the current element clicked and to only trigger that sub menu for that specific element.
I also have react icons that I used in my data file
So I'm trying to display them only if there's a sub nav
This code is the logic, but I can't seem to fit it anywhere properly
if(item.subNavExists) {
{item.downArrow}
} else if(subnav is click) {
{item.upArrow}
}
else {
return null
}
How would I fit this logic inside of my li tags?
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
Try this approach,
Create a separate component for Sidebar and track each sidebar changes separately using local state like below,
import { Link } from "#material-ui/core";
import React, { useState } from "react";
import "./styles.css";
const SidebarData = [
{
id: 1,
title: "Item1"
},
{
id: 2,
title: "Item2"
}
];
export default function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{SidebarData.map((item, index) => {
return <Sidebar item={item} />;
})}
</div>
);
}
const Sidebar = ({ item }) => {
const [selected, setSelected] = useState(false);
return (
<div class="container">
<button onClick={() => setSelected(!selected)}>
{selected ? "hide" : "show"}
</button>
<li key={item.id} className={item.cName}>
<Link to={item.path}>
{item.icon}
<span>{item.title}</span>
</Link>
</li>
{selected && <div>SUB-NAV</div>}
</div>
);
};
Sample demo - https://codesandbox.io/s/compassionate-booth-mj9xo?file=/src/App.js

React array mapping, toggles all drop-downs on click, I want to open the dropdown for the clicked card only

TextQuoteCard
import React, {useRef, useState} from 'react'
import {Link} from "react-router-dom";
import {QuoteCardDropdown} from "../../utils/dropdowns";
export const TextQuoteCard = () => {
const [open, setOpen] = useState(false)
const toggle = () => setOpen(!open)
const [textQuote, setTextQuote] = useState([
{
userId: '123',
userName: 'Tr',
userImageUrl: 'https://qph.fs.quoracdn.net/main-thumb-892821828-200-lrcgeycqieflgsovvoxglqawinbcjhtv.jpeg',
quoteId: 'TQ122',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Jhon Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'If there’s no market, about finding market opportunities, or creating opportunities. If there’s no market, then you need to grow one',
quoteImageUrl: 'https://qph.',
bookmarkStatus: 2,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
},
{
userId: '124',
userName: 'nr',
userImageUrl: 'https://qph.fi.jpeg',
quoteId: 'TQ123',
postDateTime: 'Fri',
quoteAuthorId: '123',
quoteAuthorName: 'Wall Mart',
quoteCategory: 'Motivational',
quoteType: 'textQuote',
quoteText: 'Best thing to do. ',
quoteImageUrl: '',
bookmarkStatus: 1,
likesCount: 3300,
commentsCount: 123,
overallShareCount: 1203,
fbShareCount: 423,
twtShareCount: 1232,
waShareCount: 1023,
viewCount: 1923
}
])
const handleBookmark = (event) => {
console.log(event)
}
const idGetter = (id) =>{
console.log(id)
}
const test = Object.keys(textQuote).map(item => item)
console.log(test)
return(
<div>
{
textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(!open)}
>
options
</span>
</span>
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))
}
</div>
)
}
**
QuoteCardDropdown.js
import React, {useState} from 'react'
import {Link} from "react-router-dom";
import '../../global/assets/css/dropdowns.css'
export const QuoteCardDropdown = (props) => {
const [ddItems, SetDdItems] = useState([
{
ddOptionIcon: 'icon',
ddOptionText: 'Share',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
},
{
ddOptionIcon: 'icon',
ddOptionText: 'Bookmark',
ddOptionTip: 'Tip text goes here',
ddOptionBorder: 'no',
targetId: props.targetId,
targetLink: props.targetLink
}
])
return (
<div>
<div className="quoteCardDropdownPrimaryContainer">
<div className="quoteCardDropdownPrimaryBody">
<div className="quoteCardDropdownPrimaryBodyInner">
{
ddItems.map(item => (
<Link to=
{
item.ddOptionText === 'Edit this Quote' ?
`${'edit/' + props.targetLink}` :
item.ddOptionText === 'Share' ?
`${'share/' + props.targetLink}` : ''
}
>
<div className="quoteCardDropdownContentWrapper">
<div className="quoteCardDropdownContentItem">
<div className="quoteCardDropdownItem" key={item.ddOptionText}>
{item.ddOptionText}
</div>
</div>
</div>
</Link>
))
}
</div>
</div>
</div>
<div className="quoteCardPointer" data-placement='top'> </div>
</div>
)
}
I have array of objects mapping to which showed multiple card on-page/feed. each card has a dropdown that the user can perform several actions for the clicked card. think of FB feed or any other social media feed card that the user can click to open a dropdown and pick option for the card. I am trying to achieve something similar but the problem is when I click on the button to open the dropdown it toggles all the dropdowns for all the cards instead of opening the dropdown for the clicked card.
Expected Behavior: open the dropdown for the clicked card only.
Change the open to take the id:
const [open, setOpen] = useState() // undefined is nothing open
const toggle = id => setOpen(open === id ? undefined : id) // close if currently open
// the JSX
return(
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}>
options
</span>
</span>
{open === quote.quoteId && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
))}
</div>
)
Yes, you are trying to control all dropdowns using a single state variable open.
{open && <QuoteCardDropdown targetLink={quote.quoteId}/>}
When you click on any dropdown it will toggles open and then all dropdowns will open because that single variable controls all of them.
Instead, what you can do is maintain a separate state variable for each dropdown.
I have an example to maintain separate state variable for dropdown-
toggle = (index) => {
this.setState(prevState => {
[`open+${index}`]: !prevState[`open${index}`]
}
}
This way you can keep track of/or toggles open for particular dropdown you just need to change below code -
{
textQuote.map((quote, index) => ( //add 2nd parameter as index
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span className="QuoteCardEngagementActionButtonIcon"
onClick={() => this.toggle(index)}
>
options
</span>
</span>
{ this.state[`open${index}`] && <QuoteCardDropdown targetLink={quote.quoteId}/>}
</div>
</div>
</div>
))
}
Note - Unfortunately I am not aware of handling state as dynamically inside the function component, but I have given you the exact same use case using class component.
Use an object indexed by quote ID to track a card's open status by the quote ID. I copied only the relevant code, make sure this has all of the other code you need:
export const TextQuoteCard = () => {
const [openById, setOpenById] = useState({});
const toggle = (quoteId) => {
setOpenById((currOpenById) => {
const nextOpenById = { ...currOpenById };
if (currOpenById[quoteId]) {
delete nextOpenById;
} else {
nextOpenById[quoteId] = true;
}
return nextOpenById
})
}
// ...removed code not relevant to example
return (
<div>
{textQuote.map((quote) => (
<div className="QuoteCardPrimaryContainer" key={quote.quoteId}>
<div>{quote.userName}</div>
<div className="ddContainer">
<span className="QuoteCardEngagementActionButtonIconContainer">
<span
className="QuoteCardEngagementActionButtonIcon"
onClick={() => toggle(quote.quoteId)}
>
options
</span>
</span>
{openById[quote.quoteId] && <QuoteCardDropdown targetLink={quote.quoteId} />}
</div>
</div>
))}
</div>
);
};

Resources