Changing order of list elements - javascript

I´d like to know, how can I change the order of elements in some list.
For example I would have this list below and buttons for changing order:
<button onClick={"function for moving the item up"}>Move Up</button>
<button onClick={"function for moving the item down"}>Move Down</button>
<ul>
<li>Item1</li>
<li className="selected-item">Item2</li>
<li>Item3</li>
<li>Item4</li>
</ul>
So if I would click on button "Move Up", I want item with class name "selected-item" to move up (change the order position). And exactly the same case for clicking on button "Move Down", but of course the item would move down.
Thank you.

I know a crude way of doing things.
First, make sure your elements are in a list.
Then, allow selecting a single element.
Only show the Move buttons if there's a selection.
Update the index of both the current and previous or next element, swapping them.
Due to the state update, the app gets re-rendered in the right way.
Here's a demo.
import React, { Component } from "react";
import "./styles.css";
export default class App extends Component {
state = {
items: ["Milk", "Curd", "Yogurt"],
currentSelection: -1
};
selectHandler = (idx) => {
let currentSelection = -1;
if (this.state.currentSelection !== idx) {
currentSelection = idx;
}
this.setState({
currentSelection
});
};
handleMove = (dir) => {
const { currentSelection } = this.state;
const items = [...this.state.items];
if (dir === "up") {
// Swap the currentSelection value with the previous one.
let a = items[currentSelection];
items[currentSelection] = items[currentSelection - 1];
items[currentSelection - 1] = a;
} else if (dir === "down") {
// Swap the currentSelection value with the next one.
let a = items[currentSelection];
items[currentSelection] = items[currentSelection + 1];
items[currentSelection + 1] = a;
}
this.setState({
items,
currentSelection: -1
});
};
render() {
const { items, currentSelection } = this.state;
return (
<div>
<p>Click to select and again click to deselect.</p>
<ul>
{items.map((item, key) => (
<li
key={key}
className={currentSelection === key ? "active" : undefined}
onClick={() => this.selectHandler(key)}
>
{item}
</li>
))}
</ul>
{currentSelection !== -1 && (
<div>
<p>What do you wanna do?</p>
<button
disabled={currentSelection === 0}
onClick={(e) => {
e.preventDefault();
this.handleMove("up");
}}
>
Move Up
</button>
<br />
<button
disabled={currentSelection === items.length - 1}
onClick={(e) => {
e.preventDefault();
this.handleMove("down");
}}
>
Move Down
</button>
</div>
)}
</div>
);
}
}
Demo: https://uiyq8.csb.app/
Preview

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.

Unable to Deselect the Previous item, when clicked on another Item using Reactjs

I have list of items and I am changing the background color when I clicked on that item...
I need to change only the current Selected Item only , Previous selected Item should deselect... Now here, all items are getting changed when I clicked on each item... How can I solve this?? Can anyone help me in this ??
Here is my code
const Time = () => {
const [selectedItem, setSelectedItem] = React.useState();
const handleSelect = (event,selectedItem) => {
setSelectedItem(selectedItem);
event.target.style.backgroundColor = 'black';
event.target.style.color = 'white';
};
const dataTime = useMemo(() => TimeData, []);
const chunks = useMemo(() => {
const _chunks = [];
const tmp = [...dataTime];
while (tmp.length) {
_chunks.push(tmp.splice(0, 3));
}
//console.log(_chunks);
return _chunks;
}, [dataTime]);
return (
<div className="Main-Section">
<div className="First-Section">Time</div>
<div className="Second-Section">
<div className="date_title">Wednesday , June 13</div>
<div className="time_Slots">
{chunks.map((timeslot) => {
return (
<div key={timeslot} className="inline">
{timeslot.map((time) => {
return <p className='timeslots target' key={time}
onClick={(event) => handleSelect(event,time)}>{time}</p>;
})}
</div>
);
})}
</div>
<div>{selectedItem}</div>
<div className='new_Date'>PICK A NEW DATE</div>
</div>
</div>
);
};
U can struct Ur time data to an object like this...
{text:"",selected:false}
and use selected property flag in data then assign a class conditionally into items based on that flag. then in clicking iterate Ur list and make all the flags false except the selected one.
function selectFn(selectedItem) {
data.forEach((item) => {
item.selected = false;
});
selectedItem.selected = true;
}
and UR template be some
...
{chunks.map((timeslot) => {
return (
<div key={timeslot} className="inline">
{timeslot.map((time) => {
return <p className={`timeslots target ${time.selected ? "selectedClass" : ""}`} key={time.text}
onClick={(event) => selectFn(time)}>{time.text}</p>;
})}
</div>
);
})}
...
the css stuff..
.selectedClass{
background:black;
color:white;
}
you have to first change color for all the element to default color. Then you have to change the current element color to desired color.

Div onClick function doesn't work when clicking in React app

I have a function which creates 2 divs when changing the number of items correspondingly (say if we choose 5 TVs we will get 5 divs for choosing options). They serve to make a choice - only one of two possible options should be chosen for every TV, so when we click on one of them, it should change its border and background color.
Now I want to create a dynamic stylization for these divs: when we click on them, they should get a new class (tv-option-active) to change their styles.
For that purposes I used 2 arrays (classesLess and classesOver), and every time we click on one of divs we should remove a class if it's already applied to the opposite option and push the class to the target element - thus only one of options will have tv-option-active class.
But when I click on a div I do not get anything - when I open the document in the browser and inspect the elements, the elements do not even receive new class on click - however, when I console log the classes variable that should apply to an element, it is the way it should be - "less tv-option-active" or "over tv-option-active". I applied join method to remove the comma.
I checked the name of imported css file and it is ok so the problem is not there, also I applied some rules just to make sure the problem is not there and it worked when it's not dynamic (I mean no clicks are needed).
So my list of reasons causing that trouble seems to be not working.
I also tried to reorganize the code in order to not call a function in render return - putting mapping directly to render return, but this also didn't work.
Can anyone give me a hint why it is that?
Here is my code.
import React from 'react'
import { NavLink, withRouter } from 'react-router-dom'
import './TVMonting.css'
import PageTitle from '../../PageTitle/PageTitle'
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
},
}
render() {
let classesLess = ['less']
let classesOver = ['over']
const tvHandlers = {
tvs: {
decrease: () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
},
increase: () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
},
},
}
const createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
return divsArray.map((i) => {
return (
<React.Fragment key={i}>
<div
className={classesLess.join(
' '
)}
onClick={() => {
const idx = classesOver.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesLess.splice(
idx,
1
)
}
classesLess.push(
'tv-option-active'
)
}}
>
Under 65
</div>
<div
className={classesOver.join(
' '
)}
onClick={() => {
const idx = classesLess.indexOf(
'tv-option-active'
)
if (idx !== -1) {
classesOver.splice(
idx,
1
)
}
classesOver.push(
'tv-option-active'
)
// classesOver.join(' ')
}}
>
Over 65
</div>
</React.Fragment>
)
})
}
return (
<div>
<button onClick={tvHandlers.tvs.decrease}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={tvHandlers.tvs.increase}>
+
</button>
{createDivs()}
</div>
)
}
}
export default withRouter(TVMontingRender)
CSS file is very simple - it just adds a border.
P.S. I know that with this architecture when I click on one of the divs all the divs will get tv-option-active class, but for now I just want to make sure that this architecture works - since I'm relatively new in React 🙂Thanks in advance!
Components won't have their lifecycle triggered if you are mutating a variable. You need a state for that purpose, which stores the handled data.
In your case you need some state to say which div has the active class, under or over. You can also abstract each rendered Tv to another Class component. This way you achieve independent elements that control their own class, rather than changing all others.
For that I created a Tv class, where I simplified some of the logic:
class Tv extends React.Component {
state = {
activeGroup: null
}
// this will update which group is active
changeActiveGroup = (activeGroup) => this.setState({activeGroup})
// activeClass will return 'tv-option-active' if that group is active
activeClass = (group) => (group === this.state.activeGroup ? 'tv-option-active' : '')
render () {
return (
<React.Fragment>
<div
className={`less ${ activeClass('under') }`}
onClick={() => changeActiveGroup('under')}
>
Under 65
</div>
<div
className={`over ${ activeClass('over') }`}
onClick={() => changeActiveGroup('over')}
>
Over 65
</div>
</React.Fragment>
)
}
}
Your TvMontingRender will be cleaner, also it's better to declare your methods at your class body rather than inside of render function:
class TVMontingRender extends React.Component {
state = {
tvData: {
tvs: 1,
under: 0,
over: 0,
}
}
decreaseTvs = () => {
if (this.state.tvData.tvs > 1) {
let tvData = {
...this.state.tvData,
}
tvData.tvs -= 1
this.setState({ tvData })
}
}
increaseTvs = () => {
if (this.state.tvData.tvs < 5) {
let tvData = {
...this.state.tvData,
}
tvData.tvs += 1
this.setState({ tvData })
}
}
createDivs = () => {
const divsNumber = this.state.tvData.tvs
let divsArray = []
for (let i = 0; i < divsNumber; i++) {
divsArray.push(i)
}
// it would be better that key would have an unique generated id (you could use uuid lib for that)
return divsArray.map((i) => <Tv key={i} />)
}
render() {
return (
<div>
<button onClick={this.decreaseTvs}>
-
</button>
{this.state.tvData.tvs === 1 ? (
<h1> {this.state.tvData.tvs} TV </h1>
) : (
<h1> {this.state.tvData.tvs} TVs </h1>
)}
<button onClick={this.increaseTvs}>
+
</button>
{this.createDivs()}
</div>
)
}
}
Note: I didn't change the key you are passing to Tv, but when handling an array that you manipulate somehow it's often better to pass an unique id identifier. There are some libs for that like uuid, nanoID.
When handling complex class logic, you may consider using libs like classnames, that would make your life easier.

React - change state of one button from 100 buttons array, and reset state of all buttons on one click

So, I have 100 mapped buttons from array, something like that
{
buttons.map((button, index) =>
<StyledGameButton
key={index}
value={button}
onClick={this.checkButton}>
{ button < 10 ? `0${button}` : button }
</StyledGameButton>
)
}
I want user to click all the buttons from 0 to 99, so when he click for example 0 the button should change color. I made function checking if he clicked correct button, and if yes then I am addind data-attr to that button (thats how I am changing the color of buttons):
checkButton = e => {
const buttonId = e.currentTarget.getAttribute('value');
const nextButton = this.state.currentButton === null ? 0 : this.state.currentButton + 1;
if (buttonId == nextButton){
this.setState({
currentButton: parseInt(buttonId),
});
if (this.state.currentButton === 99) {
this.gameEnd();
};
e.currentTarget.setAttribute('data-status', 'correct');
}
}
The problem is that I want to make reset button, that will change all buttons to 'unclicked' so I have to delete data-attr from all buttons on one click. How can I do this? Or is there a better solution to manage 'state' of single button without making 100 states?
100 checkboxes demo
use an Array to store the state would be fine.
const list = [...Array(100).keys()].map(x => ({ id: x }));
const App = () => {
const [selected, setSelected] = React.useState([]); // init with empty list
const onChangeHandler = id => () => { // pass index/identify params
selected.includes(id) // check whether been checked
? setSelected(selected.filter(x => x !== id)) // yes, remove
: setSelected([...selected, id]); // no, add
};
const onRemove = () => {
setSelected([]); // remove all, set to empty list
};
return (
<div className="App">
{list.map(item => (
<input
type="checkbox"
key={item.id}
checked={selected.includes(item.id)}
onChange={onChangeHandler(item.id)}
/>
))}
<br />
<button onClick={onRemove}>Remove all</button>
<div>{selected.join(",")}</div>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>

Way to check if first iteration in map

I'm trying to add a class to an element within ReactJS using the map function, but ONLY for the first one in the loop, is this possible / an easy way?
return (
<div key={itemData.itemCode} className="item active">
Want to add 'active' class when the first but for the others dont add it
</div>
)
If you use .map or .forEach then you can do it like this
var List = React.createClass({
render: function() {
var lists = this.props.data.map(function (itemData, index) {
/// if index === 0 ( it is first element in array ) then add class active
var cls = (index === 0) ? 'item active' : 'item';
return <div key={itemData.itemCode} className={ cls }>
{ itemData.itemValue }
</div>;
})
return <div>{ lists }</div>;
}
});
Example
also there is good package called classnames if you need conditionally change classes, like as in your case
var List = React.createClass({
render: function() {
var lists = this.props.data.map(function (itemData, index) {
return <div
key={itemData.itemCode}
className={ classnames('item', { active: index === 0 }) }>
{ itemData.itemValue }
</div>
})
return <div>{ lists }</div>;
}
});
Example

Categories