Add active class to li items in react - reactjs

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

Related

React - Conditionally change the style of an element based on its own position

I'm trying to change the opacity of an element based on its own position (show it only if its bigger than 500px):
export const Card = ({ pic }) => {
const card = useRef(null);
return (
<li
ref={card}
style={{opacity: 500 > card.current.getBoundingClientRect().y ? 1 : 0}}
>
<img src={pic} />
</li>
);
};
But I got this error: Uncaught TypeError: Cannot read properties of null (reading 'getBoundingClientRect')
And could not figure out how to access its position currectly.
Thanks!
You shouldn’t read the current value during rendering, as said in the documentation
During the first render, the DOM nodes have not yet been created, so
ref.current will be null. And during the rendering of updates, the DOM
nodes haven’t been updated yet. So it’s too early to read them.
You could use state instead
const Card = ({pic}) => {
const card = useRef(null);
const [y, setY] = useState(0)
useEffect(() => {
setY(card.current.getBoundingClientRect().y)
}, [])
return (
<li
ref={card}
style={{opacity: 500 > y ? 1 : 0}}
>
<img src={pic} />
</li>
);
};

Adding a specific styling to a specific item of a mapped array in React

const allTodos = [{id: 1, name: 'whatever user type'}, { }, { }, .... { }] // Defined as an array using setState in other file. Imported in this file as allTodos using props.
export const Todos = (props) => {
props.allTodos.map((prev) => {
return (
<div id="item_container">
<button type='button' className = 'check_button'
onClick = {() => setActiveTodos(prev.id)}>
<img src = {check} alt="" id = "check_img"></img>
</button>
<li id="li_item">{prev.name}</li>
</div>
)}
}
Now, the question is I want to add some specific style to the element(Button) clicked upon on. The styling I want to add is just change the background of the Button clicked upon.
I tried using conditional to set className but the styling was added to every item. So, it didn't work out.
conditional class - selecting particular class based on condition (in this case - if the activeTodos state == current index)
props.allTodos.map((prev, i) => {
<button type = 'button' key ={i}
className= {`${prev.id == activeTodos ? "active" : "inactive"}
onClick={e => setActiveTodos(prev.id)}}
</button>
}
There is some combinations (eg. There can be selected only 1 item per time or 4)
If You wan't 4 items selected (eg from 6) - then there is possiblity to hold active ids in array.

React testing-libraby not showing complete html

Recently started writing test cases for react component with very little knowledge. I have a component which contain a dropdown showing name of the fruits and a add button. After selecting a fruit from the list user have to click on Add button to show that fruit as a card (with name and image) in bottom section. Here 2 things are happening on Add button click -
A new card is added in the bottom section show image of the fruit and name
Selected fruit is removed from the dropdown list.
Sample code here - https://codesandbox.io/s/tender-paper-2i14r?file=/src/App.js
import { useState } from "react";
import "./styles.css";
const Fruits = ["apple", "orange", "banana"];
const FruitList = ({ onChange, fruitList = [] }) => {
return (
<select onChange={onChange} aria-label="selectFruitFromList">
<option selected>Select fruit</option>
{fruitList.map((fruit) => (
<option value={fruit}>{fruit}</option>
))}
</select>
);
};
const SelectedFruit = ({ selectedFruit }) => {
return (
<ul>
{selectedFruit.map((item) => (
<li aria-label="selectedFruits">{item}</li>
))}
</ul>
);
};
export default function App() {
const [selected, setSelected] = useState();
const [fruitList, setFruitList] = useState(Fruits);
const [selectedFruit, setSelectedFruit] = useState([]);
const addFruitToTray = () => {
setSelectedFruit([...selectedFruit, selected]);
setFruitList(fruitList.filter((item) => item != selected));
setSelected("");
};
return (
<div className="App">
<h1 aria-label="hello">Hello CodeSandbox</h1>
<div>
<FruitList
onChange={(e) => setSelected(e.target.value)}
fruitList={fruitList}
/>
<button
onClick={addFruitToTray}
disabled={selected ? false : true}
aria-label="AddFruit"
>
Add
</button>
</div>
<div>
<SelectedFruit selectedFruit={selectedFruit} />
</div>
</div>
);
}
test("Select fruit and Add", ()=> {
const { debug } = render( <App/> );
await userEvent.selectOptions(screen.getByLabelText('selectFruitFromList'), 'apple' )
expect(screen.getByLabelText('AddFruit').disabled).toBeFalsy()
await userEvent.click(screen.getByLabelText('AddFruit'))
await userEvent.selectOptions(screen.getByLabelText('selectFruitFromList'), 'banana' )
await userEvent.click(screen.getByLabelText('AddFruit'))
debug()
// Test fails here
await waitFor(() => {
expect(screen.getAllByLabelText('selectedFruits')).toBeInTheDocument();
});
})
Problem -
It fails on my last test where I'm checking li for added fruits. I have check it with debug and the HTML shows empty <ul> tag (means no <li> created). The debug error shows TestingLibraryElementError: Unable to find a label with the text of: selectedFruits
Another point I see all 3 items in the dropdown element in debug html. Ideally it shouldn't be as when we click on Add button that item gets removed from dropdown.
Any help will be appreciated. Thank you.
I have generated new test project with npm init react-app testing and i have tried your code. It gives this error:
expect(received).toBeInTheDocument()
received value must be an HTMLElement or an SVGElement.
Received has type: array
Received has value: [<li aria-label="selectedFruits">apple</li>, <li aria-label="selectedFruits">banana</li>]
In order to make it work use .toHaveLength(2) assertation. I have tried the same in codesandbox, but for some reason I could not make it work.
For more assert methods, this is good place to look: https://github.com/testing-library/jest-dom

React State Hook - toggle a class

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>

In a React Component, how to conditionally show a style while iterating over a list

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

Resources