only one active element - javascript

I have a redux store where is rooms array stored (fetched from server) and in my component i fetch it from the store, map it and then display elements with value of room['foo'] but my problem is this: when the components are mapped and displayed the value of one that user clicks is going to be sent to server so i store clicked elements value in components local state like this:
...
handleRoomSelection(roomNumber,index,e){
this.setState({
room: roomNumber
})
}
...
{this.props.rooms.map((val,i)=>{
return (
val.reserved === false ? <p className="" key={i} onClick={e => this.handleRoomSelection(val.roomNumber,i,e)}>{val.roomNumber}</p> : null
)
})}
and this works fine, but my problem is that i want to add className "active" to active element (there can only be one active element) it would be easy if there could be many active elements (i would just add e.target.className = "active" after setState) so how can i achieve my aim?

Basically just do what you stated: set the class based on the room number in your state. setState triggers render(), so enrich even though there's nothing active on first render, once you click, render triggers and we can just set that class inside your map:
render() {
return this.props.rooms.map(val => {
if (val.reserved) return null;
return <p
className={ this.state.room === val.roomNumber && `active`}
key={val.roomNubber}
onClick={() => this.handleRoomSelection(val.roomNumber)}
>{val.roomNumber}</p>;
};
}
Note that we've changed the key to something that uniquely identifies the element we're building a <p> for: never use array index as key, because array position does not identify any specific element, it only identifies a position in a list.
Also note that there's no reason to pass more than just the room number in your click handler if the handler itself only cares about the room number.

Compare the room in state to each room's roomNumber for defining the className
{
this.props.rooms.map((val, i) => {
const className = this.state.room === val.roomNumber ? 'active' : '';
return val.reserved === false ? (
<p
className={className}
key={i}
onClick={e => this.handleRoomSelection(val.roomNumber, i, e)}
>
{val.roomNumber}
</p>
) : null;
});
}

Have you tried to use your state to populate className ?
{this.props.rooms.map((val,i)=>{
return (
val.reserved === false
? <p
className={val.roomNumber === this.state.room ? 'active' : ''}
key={i}
onClick={e => this.handleRoomSelection(val.roomNumber,i,e)}>
{val.roomNumber}
</p>
: null
)
})}

Related

Apply 'selected' class to first element in React component on page load

I have a 'RadioItem' component which applys a 'selected' class via an 'isChecked' boolean prop when it's clicked:
{categoryFilter.map((item) => {
return (
<RadioItem
isChecked={selectedItem.id === item.id}
key={item.id}
itemName={item.name}
itemPrice={item.price}
onClick={() => clickHandler(item)}
/>
);
})}
const RadioItem = (props) => {
const clickHandler = () => {
props.onClick();
};
return (
<li
className={props.isChecked ? "item selected" : "item"}
onClick={clickHandler}
>
<div className="item__body">
<div className="item__radio"></div>
<h3 className="item__name">{props.itemName}</h3>
</div>
{props.itemPrice !== 0 && (
<p className="item__price">£{props.itemPrice}</p>
)}
</li>
);
};
I'd like to apply this 'selected' class to the first item (i.e. where the index is 0) when the page first loads but for it to then be deselected when another item is clicked. I have tried, for example, applying an OR condition to the isChecked, like this:
isChecked={selectedItem.id === item.id || index === 0}
but then of course the selected class persists on that first item regardless of me clicking other items.
Any help greatly appreciated, thanks.

How to set isActive on active link in a map loop using NEXTUI navbar?

I'm using the new NEXTUI navbar: https://nextui.org/docs/components/navbar
I want to set isActive property on the active link and there isn't much help to get from Google so I hope someone here have used it or knows how to do so. I'm using Next.js
A portion of the code:
<Navbar.Content
enableCursorHighlight
activeColor="primary"
hideIn="xs"
variant="highlight-rounded"
>
{navigation.map((item, index) => (
<Navbar.Link key={index} href={item.link}>
{item.title}
</Navbar.Link>
))}
</Navbar.Content>
EDIT: When I add isActive in the map loop, it effects all. I want to set isActiveon the clicked (active link) one at a time. If I didn't loop my nav links (which is coming from backend) I could set IsActive property on one but then its just that one that have isActive even if I click on other links.
You have to use a specific condition in your map to check if you are on the correct route.
For example: you can use the next/router and compare it to the link property of the item.
const { asPath } = useRouter();
.
.
.
//inside return body
....
{navigation.map((item, index) => {
if(asPath === item.link)
return(<Navbar.Link isActive key={index} href={item.link}>
{item.title}
</Navbar.Link>);
});
else
return(<Navbar.Lin key={index} href=. {item.link}>
{item.title}
</Navbar.Link>);
})
}
similar to the answer above but without the if/else in the return
{navigation.map((item, index) => (
<Navbar.Link
isActive={asPath === item.link}
key={index}
href={item.link}
>
{item.title}
</Navbar.Link>);
))}

How to make certain event happen on a specific item when mapping through an array?

I am mapping through an array and displaying its data. I have edit button to modify that data. When I click edit button, dropdown shows and I am able to edit as shown in screenshot I shared. Open this screenshot
Problem is all of edit buttons work even when I click any one, I want only clicked edit button to work rather than all. How can I achieve this functionality?
This is my code:
const [show, setShow] = useState(false);
const handleShow = () => { setShow(!show); };
<p onClick={handleShow}>
Edit
{show === true ? (
<IoIosArrowUp style={{ color: "#F1BB0F" }} />
) : (
<IoIosArrowDown style={{ color: "#F1BB0F" }} />
)}
</p>
{show && (
<div>
<p>
Unlocks: {lockParams[index]?.endDateString}
</p>
<p>
Unlocker : <span>{lockParams[index]?.unlocker}</span>
</p>
</div>
)}
If you have N different buttons, and you want the view to be different depending on which of those buttons are toggled on or off, you should have state corresponding to those N buttons. An array of booleans would make sense here. That way, if, say, the 4th button is toggled, you can update the 4th element in the state array, and then when the component renders, while iterating over the 4th index, it can look at that 4th boolean to see what should be shown there.
You haven't shown the necessary code in the question, but, for example, if you have:
someArray.map((item) => (
// some JSX
<p onClick={handleShow}>
// etc
Then you need to initialize a state array containing the same number of elements as someArray, like this:
const [shows, setShows] = useState(() => someArray.map(() => false));
const handleShow = (i) => setShows((show, j) => j === i ? !show : show);
And then use index when rendering:
someArray.map((item, i) => (
// some JSX
<p onClick={() => handleShow(i)}>
Edit
{shows[i] ? (
<IoIosArrowUp style={{ color: "#F1BB0F" }} />
) : (
<IoIosArrowDown style={{ color: "#F1BB0F" }} />
)}
</p>
{shows[i] && (
<div>
<p>
Unlocks: {lockParams[index]?.endDateString}

How to scroll between sibling components in React?

I want to be able to scroll between table components in my React app. I have created a component for a table called FormattedTable which takes in props and displays all the information that I want.
A lot of the tables refer to other tables with clickable text. If you click on a reference to another table and it is not being displayed, I add the table to the display and the app automatically scrolls down to the bottom of the screen where the table has been added. However, if the table is already being displayed, I want the app to scroll to where it is being displayed already.
The clicking on the reference and adding another table all occurs in the FormattedTable.js file.
In my Home.js I have an array of objects called selected and this array contains all the objects that I want to be displayed in tables. I display the tables by mapping through the selected array creating a FormattedTable component on each iteration.
Home.js
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected}/>
</div>
)
})
: null}
</div>
Because the FormattedTables are being created dynamically in the Home.js file, I'm not sure how to scroll from one table to another in FormattedTable.js (since there is only 1 file but multiple instances).
Does anyone know how this would be possible to do in the FormattedTable.js file?
What I've tried so far is added a ref to the div that's being dynamically created in Home.js and also passed in a triggerScroll method to the FormattedTable component so that I can trigger the scroll when a reference is clicked on a table. The issue with this though is that it still scrolls to the last element as the value of the ref is (naturally) the last element of the array when the mapping stops.
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div ref = {scrollRef} style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected} triggerScroll={scrollToTable}/>
</div>
)
})
: null}
</div>
Fixed myself:
Added an attribute to each object in the selected array called inFocus. The most recently selected object in the array has a value of true for this attribute.
I then added a ternary to setting the ref of the FormattedTable based on the inFocus attribute so only one object will be set as the ref at a time.
FormattedTable.js
//For selecting results
const select = (name) => {
//Deep clone object so that results doesn't change when selected changes
const obj = cloneDeep(data.find(element => element.name === name));
const refObj = data.find(element => element.name === name);
//If object is in the original JSON array
if(typeof refObj !== 'undefined') {
//If object is not in the selected array, add it to selected array
if(selected.find(element => element.name === name) === undefined) {
//Make the new object in focus and remove the focus for all the other objects
obj.inFocus = true;
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
}
})
handleSelectedChange([...copy, obj]);
}
//Otherwise, set focus to selected object
else {
//Make the selected object in focus and remove the focus for all the other objects
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
} else {
el.inFocus = true
}
})
handleSelectedChange([...copy]);
}
}
}
return (
<div ref={obj.inFocus ? messagesEndRef : null} style={{display: 'flex', flexDirection: 'row'}}>
...
)

:selected state on looped react component

I am basically wanting to do individual selected states on divs that I am rendering in a loop. I can only see a way to change the color of all of the rendered divs, but rather I wish to change the color of which ever one was clicked. Below is the code for the loop.
renderSports() {
const {sports} = this.props
return sports.valueSeq().map(sport => this.renderActualSports(sport))
},
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div className="sportSeparator">
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
This will basically just render a list of some sports. I want to change the color of a selected sport on click.
You will need to store the items that were clicked in your component state.
Assuming you would store this highlighted items in this.state.highlighted and that your sport variable is a string or number:
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div
className="sportSeparator"
onClick={this.highlight(sport)}
style={{color: this.state.highlighted.indexOf(sport) > -1 && 'red' : ''}}
>
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
highlight(sport) {
return () => {
this.setState({highlighted: [...this.state.highlighted, sport]});
}
}
So what you are doing is onClick on the div you add that sport to the this.state.highlighted array and when displaying the list. you check if that sport is in the array and if yes you change the color using an inline style

Categories