Setting "active" className for the menu user selected in React - javascript

Just like the screenshot above, I am using Semantic-UI where the selected menu gets highlighted.
As my code below, I've set state to whichever menu the user clicks on. My code below works fine, but I think this is inefficient ways to write code since I am basically calling two functions everytime I render just to change the switch out the class names.
Would there be any better way to achieve this?
Please advise.
const Navigation = () => {
const [selected, setSelected] = useState("Comments");
const onSelection = (e) => {
setSelected(e.target.textContent);
};
const commentActive = () => {
return selected === "Comments" ? "active" : "";
};
const searchActive = () => {
return selected === "Search" ? "active" : "";
};
return (
<div className="ui secondary pointing menu">
<a className={`item ${commentActive()}`} onClick={(e) => onSelection(e)}>
Comments
</a>
<a className={`item ${searchActive()}`} onClick={(e) => onSelection(e)}>
Search
</a>
</div>
);
};

I think you shouldn't hardcode the boolean conditions selected === 'Comments' as its bug prone, because if you decide to change the anchor's context without changing the condition you may have a bug: target.textContent !== 'Comments';
Instead use enums:
const NAV_SECTIONS = {
COMMENTS: "comments",
SEARCH: "search",
};
const Navigation = () => {
const [selected, setSelected] = useState(NAV_SECTIONS.COMMENTS);
return (
<div className="ui secondary pointing menu">
<a
className={`item ${selected === NAV_SECTIONS.COMMENTS ? "active" : ""}`}
onClick={() => setSelect(NAV_SECTIONS.COMMENTS)}
>
{/* We changed the content and the code didn't break */}
My Cool Comments
</a>
<a
className={`item ${selected === NAV_SECTIONS.SEARCH ? "active" : ""}`}
onClick={() => setSelect(NAV_SECTIONS.SEARCH)}
>
My Best Search
</a>
</div>
);
};

Related

How to toggle css class in a component based on 3 different state elements

I have a Card component that has 4 potential states: "active", "selected", "discarded", "complete". Depending on the state, I need to display a different css class.
The states are set by clicking on specific parts of the card and each part is a "toggle" (one click sets it, another click "unsets" it):
default state is active,
if the user click on the card, it gets selected (the css class I need to add is "overlay-selected")
if the user click on "discard" btn inside the card it sets to "discarded" (the css class I need to add is "overlay-discarded" )
if the user click on "complete" btn inside the card it sets to "complete" (the css class I need to add is "overlay-complete" )
The state is stored at the "App" level and I am passing it to the component through props. This means that for updating the states I am passing some "handler" from the app to the component (SelectCard, DiscardCard)
Here below is my code.
I think they way I am approaching the problem is not ideal because I am having problem on how to best defyining the ClassName of the variable.
I initially thought about using a ternary if statement, but with more than one status it doesn't work. Especially because I need to take care of the "toggle" use-case.
App
function App() {
const [people, setPeople] = useState(data['people'].map(
el=>{
return {
...el,
'key' : nanoid(),
}
}
))
function SelectCard(CardKey){
setPeople(oldPeople=>{
return oldPeople.map(el=>{
return el.key === CardKey
? { ...el, 'selected':!el.selected}
: { ...el, 'selected':false}
})
})
}
function DiscardCard(CardKey){
setPeople(oldPeople=>{
return oldPeople.map(el=>{
return el.key === CardKey
? { ...el, 'active':!el.active}
: { ...el}
})
})
}
const cards = people.map(el=>{
return <Card
key = {el.key}
item={el}
onPress={()=>SelectCard(el.key)}
onDiscard={()=>DiscardCard(el.key)}
/>
})
return (
<div className="App">
<div className='container'>
<div className='left'>
<div className='cards'>
{cards}
</div>
</div>
<div className = 'right'>
....
</div>
</div>
</div>
)
}
export default App
Card
function Card(props) {
const className = `card ${props.item.selected ? "overlay-selected" : ""}`
return (
<div
className={className}
onClick={(event)=>{props.onPress()}}>
<img className='card-img' alt='Card Image' src={props.item.img} />
<h3 className='card-title'>{props.item.name} </h3>
{ props.item.selected ?
<div className='card-cta'>
<button
className='btn btn-back'
onClick={ props.item.selected ? (event)=>
{
event.preventDefault()
props.onPress
}
: ()=>{}}
>Back</button>
<button
className='btn btn-discard'
onClick={ props.item.selected ? (event) =>{
event.preventDefault()
props.onDiscard
}
:
()=>{}}
>Discard</button>
</div>
:
<p className='card-description'>{props.item.description} </p>
}
</div>
)
}
Best way is remove all classes (or all style interactive classes) and add the state class after the remove:
User do first action:
remove classes
add overlay-selected
User do discard action:
remove classes
add overlay-discard
User do discard action:
remove classes
add overlay-complete

Add active border to only a single item on click

Clicking on an article I want to add a 'selected' border around it:
const [selected, setSelected] = useState(false);
const handleClick = (evt) => {
setSelected(true)
};
<article
onClick={handleClick}
className={`${selected ? "border-red-500" : ""} ... `}
>
This works great for a single article but how would I go about it if I had multiple articles? I only want to have one selected at a time. This method would obviously add a border around every article when clicked.
If you want to use multiple articles, You can use map to render and setSelected is id/index of article:
const handleClick = (id) => {
setSelected(id !== selected ? id : "")
};
listArticles.map((item, index) => (
<article
key = {item.id} // or index
onClick={() => handleClick(item.id)} //or index
className={`${selected === item.id ? "border-red-500" : ""} ... `} // or index
>
))
This usually looks something like this:
const [selected, setSelected] = useState(false);
list.map((body, i) => <article key={i}
onClick={() => setSelected(i)}
className={`${selected == i ? "border-red-500" : ""} ... `}
>{body}</article>)
But it depends a little on how you store, render, and identify your list of things (in this case articles).

Button Click In React

I have a problem with React.js. This is the line of code I have:
import React, { useState } from "react";
import { map } from "lodash";
function Steps({ procedure, commandSender, index }) {
const [selected, setSelected] = useState([]);
function clickHandler(command, key, index) {
commandSender(`${command}`)
if (isSelected((index-key))) setSelected(selected.filter(s => s !== (index-key)))
else ([...selected, (index-key)])
}
function isSelected(key) {
return selected.includes(key);
}
return (
<>
{procedure.guide &&
map(procedure.guide, (key) => (
<a
key={`${index}-${key}`}
className={`bt-blue ${isSelected(index-key) ? "active" : ""}`}
onClick={() => clickHandler('GUIDE', key, index)}
>
{procedure.title}
</a>
))
}
{procedure.success &&
map(procedure.success, () => (
<a
key={`${index}-${key}`}
className={`bt-green ${isSelected(index-key) ? "active" : ""}`}
onClick={() => clickHandler('SUCCESS', key, index)}
>
{procedure.title}
</a>
))
}
</>
);
}
export default Steps;
As you can see, I map a procedure, and for each item, I create an A tag, that calls a function clickHandler. This function calls another function and a setSelected. The setSelected function says which A tag is clicked or not. The only problem is that when I click in an A tag, it doesn't get selected.
But I need just the tag I clicked to have a SELECTED effect. I think for you guys it's a very easy error to correct, but I'm really a newbie with React. Please help.
I believe the problem is the data structure you are using for storing selected values. Right now, it's a plain boolean, and you are dealing with an array.
You could do the following:
First, we change the selected to an array.
const [selected, setSelected] = useState([]);
Then, how can we identify each procedure in a unique way? Do they have an ID? By Title? Command? Let's suppose it's by title.
function clickHandler(title, command) {
commandSender(`${command}`)
if(selected.includes(title)) {
setSelected(selected.filter(s => s !== title)) // we unselected our procedure
} else {
setSelected([...selected, title]) // we add our procedure to the selected
}
}
Finally, you should change the rendering of your procedures, and remove the useEffect, as it's unnecessary.
<a
className={`bt-green ${selected.includes(procedure.title) ? "active" : ""}`}
onClick={() => clickHandler(procedure.title, 'SUCCESS')}
>
{procedure.title}
</a>
Furthermore, you could create a function to determine if your procedure is selected, so you don't have to write every time selected.includes... :
function isSelected(procedure) {
return selected.includes(procedure);
}

How to toggle 'className' dynamically in react.js?

I want to set the active class dynamically in react.js but it's not working!
I'm using the setState() method to change the selected item.
this line of code not work .
className={selectedCategoryId === item.id ? 'active' : ''}
I think the setState() function does not work correctly...
const {open, selectedProduct, productCategory, filteredProducts, selectedCategoryId} = this.state;
const categoryItems = productCategory.map((item) =>
<a key={item.id}
onClick={() => this.handleFilter(item.id)}
className={selectedCategoryId === item.id ? 'active' : ''}
// className={()=>this.isActive(item.id)}
className="pointer"
>{item.value}</a>
);
this does not change the class:
handleFilter = (id) => {
const filteredItem = this.state.productList.filter(x => x.categoryId == id);
this.setState({filteredProducts: filteredItem, selectedCategoryId: id});
}
but this change the className correctly when select I all tags:
handleRemoveFilter = () => {
this.setState({filteredProducts: this.state.productList, selectedCategoryId: 0});
}
//-------------------------------
<div className="tag-list">
<a onClick={this.handleRemoveFilter}
className="pointer"
className={ this.state.selectedCategoryId === 0 ? 'active' : ''}
>All tags</a>
{categoryItems}
</div>
If setState() works well, try this :
<a onClick={this.handleRemoveFilter}
className={ this.state.selectedCategoryId === 0 ? 'pointer active' : 'pointer'}
>All tags</a>
One of the most common ways is to use classnames which you can conditionally joining classNames together
var classNames = require('classnames');
class Button extends React.Component {
// ...
render () {
var btnClass = classNames({
btn: true,
'btn-pressed': this.state.isPressed,
'btn-over': !this.state.isPressed && this.state.isHovered
});
return <button className={btnClass}>{this.props.label}</button>;
}
}
store classname in state along with selected item. You can just update the classname in state whenever required.
for eg,
<a key={item.id}
onClick={() => this.handleFilter(item.id)}
className={this.state.activeClassName}
where active classname can be updated inside handlefilter
We can toggle class name dynamically like below,
const [islight, setIslight] = useState(false)
const toggle = () => {
setIslight(!islight)
}
return (
<div className={`block ${islight ? "blocklight" : "blockdark"}`}>
<h2>Hello World</h2>
</div>
)

Controlled Input onChange Event Fires Only Once - React

Only one key press is registered, then the input loses focus however I can click back into the component and add ... one character. State is updated.
Since the child components are state-less I assumed that just passing the handlers down as props as described in the docs would be sufficient, but everything from changing the binding for the method to adding a constructor have yielded the same results. I can usually find an answer on the site but no luck this time.
const list = [
{
id : 0,
title : "Went well",
showCard : false,
cards : [],
color : "pink"
},
{
id : 1,
title : "To Improve",
showCard : false,
cards : [],
color : "yellow"
},
{
id : 2,
title : "Action Items",
showCard : false,
cards : [],
color : "blue"
}
]
class App extends Component {
state = {list : list, usrInpt : ""}
handleChange = e => {this.setState({usrInpt:e.target.value})}
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
this.setState({list:updatedList})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}
render() {
return (
<div className="App">
<h2>Metro Board</h2>
<ul className="container">
{this.state.list.map((item) =>
<List key={(Math.random() * 1)} text={item.title}
id={item.id} cards={item.cards} color={item.color}
usrInpt={this.state.usrInpt} add={this.add} handleChange={this.handleChange}/>
)}
</ul>
</div>
)
}
}
const List = (props) =>{
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <ListItem key={(Math.random() * 1)}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
}
const ListItem = (props) =>{
console.log("card props and value",props)
return <li>
<div className="card" style={{backgroundColor: props.color}}>
<textarea type="text"
className="card"
placeholder="Enter text here"
value={props.usrInpt}
onChange={e => props.handleChange(e)}>
</textarea>
<div><a className="ltCtl" href="./logo" onClick={e => console.log(e)}><</a>
<a className="clCtl" href="./logo" onClick={e => console.log(e)}>x</a>
<a className="rtCtl" href="./logo" onClick={e => console.log(e)}>></a>
</div>
</div>
</li>
}
Both List && ListItem are separate files... Any help would be great. Thanks.
UPDATE:
I was able to reach out to a full time developer and it seems I screwed up by trying to make unique keys :
The key needs to be consistent, but in this case it is a different value every time
React uses the key when it IDs which element is focusing on, but in this case, it is different than the last render. So React does not know what to focus on. You can have unique keys if you use a string with the index of the loop in it, or if you use an ID that you store outside in the loop, like in state
It does need to be unique, but it also needs to be consistent.
So the code:
return (
<Card
key={Math.random() * 1} // <--- Don't!!
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}
/>
);
was causing React to lose track of what to render since the keys where changing with each render. The preferred method is using a string interpolation with an identifier and an index if a loop is used:
return(
<li>
<h3>{props.text}</h3>
<ul className="stack">
<li><button id={props.id} type="button" className="block" onClick={e =>props.add(e)}>+</button></li>
{props.cards.map((card,i)=> {
console.log("card",card)
return <Card key={`card-column-${props.id}-${i}`}
idx={i}
color={props.color}
handleChange={props.handleChange}
usrInpt={props.usrInpt}/>
})}
</ul>
</li>
)
Which was also a comment made by #miyu ... which I did not test. Listen to your peers and mentors... you will not lose 12 hours chasing bugs. Thanks.
state is non-hierarchical. Meaning, when you update a child object of your state but the parent object is not updated, then react will not trigger componentDidChange.
Try adding a counter which gets updated when the list is updated.
add = e => {
e.preventDefault()
let updatedList = this.state.list.map(obj => {
if(obj.id == e.target.id)
this.state.list[obj.id].cards.push(this.state.usrInpt)
return obj
})
console.log("uL",updatedList)
let counter = this.state.counter || 0;
this.setState({list:updatedList, counter: counter++})
//this.setState(this.state.list[e.target.id].cards = [...this.state.list[e.target.id].cards,"pp"])
}

Categories