how to add class and remove class from buttons in react? - javascript

I have 20 buttons and I wanted to apply class .active on the button which is clicked and the rest will inactive. Suppose I clicked on button one then I want to add an active class to button one and then when I clicked on button two then button two will get an active class and active class removed from button one.
import React from "react";
const PaginationButtonsList = (props) => {
const handleClick = (event) =>{
}
return (
<div className="pagination-buttons-list">
<button onClick={handleClick} id="button-1">1</button>
<button onClick={handleClick} id="button-2">2</button>
<button onClick={handleClick} id="button-3">3</button>
<button onClick={handleClick} id="button-4">4</button>
<button onClick={handleClick} id="button-5">5</button>
<button onClick={handleClick} id="button-6">6</button>
<button onClick={handleClick} id="button-7">7</button>
<button onClick={handleClick} id="button-8">8</button>
<button onClick={handleClick} id="button-9">9</button>
<button onClick={handleClick} id="button-10">10</button>
<button onClick={handleClick} id="button-11">11</button>
<button onClick={handleClick} id="button-12">12</button>
<button onClick={handleClick} id="button-13">13</button>
<button onClick={handleClick} id="button-14">14</button>
<button onClick={handleClick} id="button-15">15</button>
<button onClick={handleClick} id="button-16">16</button>
<button onClick={handleClick} id="button-17">17</button>
<button onClick={handleClick} id="button-18">18</button>
<button onClick={handleClick} id="button-19">19</button>
<button onClick={handleClick} id="button-20">20</button>
</div>
);
};
export { PaginationButtonsList };

I assume that you don't want a button with just generic numbers for text. So you will need to:
create an array list with all text that you want to set to the button
Then render all of it through the map and bind the onClick event to take the index
on click you should set that index in state and check which button
has that index so set it to active
.
import React, {useState} from "react";
/* Change this number to any text and add as many as you need */
let buttonText = ['1','2','3','4','5']
const PaginationButtonsList = (props) => {
const [activeIndex, setActiveIndex] = useState(-1)
const handleClick = (value) =>{
setActiveIndex(value)
}
return (
<div className="pagination-buttons-list">
{buttonText.map((text,index)=> (
<button onClick={()=>handleClick(index)} class={index === activeIndex ? "active" :""} id={`button-${index}`}>{text}</button>
)
</div>
);
};
export { PaginationButtonsList };

One way of approaching this would be to create an array of button objects that you can use to configure your component. Each button object in the array would have the shape { id: number, text: string, active: boolean } that defines it. You can that add that configuration to state.
When a button is clicked you reset the active values of each button (by updating a deep-copy the current state), update the active value of the clicked button, and finally create a new state with the updated data. That new state will be reflected in the component when it's re-rendered.
This method also has the advantages that 1) you encapsulate the button config in one place without the need for separate states, and 2) you don't need to hard-code all the buttons in the JSX - you can map over the button configuration to create an array of buttons using a useful Button component.
const { useState } = React;
// Pass in the button config
function Example({ btnConfig }) {
// Set state with the config
const [ btns, setBtns ] = useState(btnConfig);
// When a button is clicked grab its id from its dataset,
// make a deep copy of the state resetting all of the active
// values for each button to false, find the index of the button
// that was clicked, and then set its active value to true.
// Finally update the state to re-render the component
function handleClick(e) {
const { id } = e.target.dataset;
const reset = btns.map(btn => ({ ...btn, active: false }));
const index = reset.findIndex(btn => btn.id === +id);
reset[index].active = true;
setBtns(reset);
}
// `map` over the state and create JSX using a
// Button component passing down the properties from
// the objects in state as well as a reference to the
// `handleClick` function
return (
<div>
{btns.map(btn => {
const { id, text, active } = btn;
return (
<Button
key={id}
id={id}
active={active}
text={text}
handleClick={handleClick}
/>
);
})}
</div>
);
}
// Accepts the button props and returns a button. If
// the active value is true then apply the "active" class
function Button({ id, text, active, handleClick }) {
return (
<button
data-id={id}
onClick={handleClick}
className={active && 'active'}
>{text}
</button>
);
}
// Create a button config - an array of button of objects
const btnConfig = Array.from({length: 10}, (_, i) => {
const id = i + 1;
return { id, text: id, active: false };
});
// Pass in the button config to the component
ReactDOM.render(
<Example btnConfig={btnConfig} />,
document.getElementById('react')
);
button { margin-right: 0.5em; font-size: 1.2em; border-radius: 5px; padding: 0 0.4em; }
button:hover { cursor: pointer; background-color: #cdcdcd; }
button.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

You only need to save a single integer of state, the index corresponding to the active button -
function App({ buttons = [] }) {
const [active, setActive] = React.useState(-1)
const toggle = index => event => setActive(index)
return <div>
{buttons.map((b, key) =>
<button
className={active == key && 'active'}
onClick={toggle(key)}
children={b}
/>
)}
</div>
}
ReactDOM.render(
<App buttons={["🍟","🥨","🍐","🌮","🥤"]} />,
document.body
)
button { margin-right: 0.5em; font-size: 1.2em; border-radius: 5px; padding: 0 0.4em; }
button:hover { cursor: pointer; background-color: #cdcdcd; }
button.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
If you want repeated clicks to the same button to toggle the active off, if the same button is clicked twice, you can restore the initial active state of -1 -
function App({ buttons = [] }) {
const [active, setActive] = React.useState(-1)
const toggle = index => event =>
setActive(index == active ? -1 : index) // <-
return <div>
{buttons.map((b, key) =>
<button
className={active == key && 'active'}
onClick={toggle(key)}
children={b}
/>
)}
</div>
}
ReactDOM.render(
<App buttons={["🍟","🥨","🍐","🌮","🥤"]} />,
document.body
)
button { margin-right: 0.5em; font-size: 1.2em; border-radius: 5px; padding: 0 0.4em; }
button:hover { cursor: pointer; background-color: #cdcdcd; }
button.active { background-color: lightgreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>

You can create an array state holding information for each button.
In each button state object in the array, include a property for whether the button is active or not. Then use this property when mapping the button states to actual buttons and set the active class if the button's state is active.
When a button is clicked, update the state array: make that button's state active and make every other button's state inactive.
Here's a complete example with types:
import {default as React, type ReactElement, useState} from 'react';
type ButtonState = {
active: boolean;
id: number;
};
const PaginationButtonsList = () => {
const [buttonStates, setButtonStates] = useState<ButtonState[]>(Array.from(
{length: 20},
(_, i) => ({id: i + 1, active: false}),
));
const createButtonFromState = (
{active, id}: ButtonState,
index: number,
): ReactElement => {
const setActiveExclusive = () => {
setButtonStates(arr => arr.map((state, i) => ({
...state,
active: i === index,
})));
};
const buttonProps = {
className: active ? 'active' : '',
id: `button-${id}`,
key: id,
onClick: setActiveExclusive,
};
return (<button {...buttonProps}>{id}</button>);
};
return (
<div className="pagination-buttons-list">
{buttonStates.map(createButtonFromState)}
</div>
);
};
Code in the TypeScript Playground

Related

React Toggle Body Class with button

I'm still learning React but I'm having an issue toggling a body class with a button in the menu.
const toggleSideMenu = event => {
// toggle class on click
//Below is not correct
event.getElementsByTagName('body').classList.toggle('sb-sidenav-toggled');
};`
<button onClick={toggleSideMenu} id="sidebarToggle" href="#!"><i className="fas fa-bars"></i></button>
I'm used to doing this easily in jQuery but it's not recommended to use jQuery in React because of the dom. I would appreciate any suggestions.
Thanks so much!
In this example, we are using the useState hook to keep track of the toggle state. The initial state is set to false. We are using the isToggled state in the JSX to determine what to render on the screen, and to update the text of the button.
We have an onClick event on the button, which calls the setIsToggled function and pass the negation of the current state (!isToggled), this is the way to toggle the state, every time the button is clicked.
import React, { useState } from 'react';
const MyComponent = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<div>
{/* render some content or change className based on the toggle state */}
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
export default MyComponent;
But if you need to do something more advanced, maybe you can learn more about React Context.
import React, { useState } from 'react';
// Create a context to share the toggle state
const ToggleContext = React.createContext();
const MyApp = () => {
// useState hook to keep track of the toggle state
const [isToggled, setIsToggled] = useState(false);
return (
<ToggleContext.Provider value={{ isToggled, setIsToggled }}>
<MyComponent1 />
<MyComponent2 />
{/* any other components that need access to the toggle state */}
</ToggleContext.Provider>
);
}
const MyComponent1 = () => {
// use the toggle state and toggle function from the context
const { isToggled, setIsToggled } = useContext(ToggleContext);
return (
<div>
<p className={isToggled? "class1" : "classB">Toggled on</p>
<button onClick={() => setIsToggled(!isToggled)}>
{isToggled ? 'Turn off' : 'Turn on'}
</button>
</div>
);
}
const MyComponent2 = () => {
// use the toggle state from the context
const { isToggled } = useContext(ToggleContext);
return (
<div>
{isToggled ? <p>Toggled on</p> : <p>Toggled off</p>}
</div>
);
}
export default MyApp;
A very basic example to show you how to use state to maintain whether a menu should be open or not.
It has one button that when clicked calls a function that updates the state.
It has one Menu component that accepts that state, and uses CSS to determine whether it should be "open" (ie on/off screen).
Like I said, as simple as I could make it.
const { useState } = React;
function Example() {
// The state set to either true or false
// Initially it's false / menu closed
const [ menuOpen, setMenuOpen ] = useState(false);
// When the button is clicked we take the
// previous state and toggle it - either from true
// to false, or vice versa
function handleClick() {
setMenuOpen(prev => !prev);
}
// One Menu component that accepts that state
// and one button that updates the state
return (
<div>
<Menu open={menuOpen} />
<button onClick={handleClick}>
Toggle Sidebar Menu
</button>
</div>
);
}
// Small menu (an aside element) which uses CSS
// to work out its position on the screen
// It does this by creating a classList using the default
// "menu" which it ties together with "open" but it only
// adds that if the state is true
// And then just use that joined array as the className on
// the element
// You can see in the CSS what both those classes do
function Menu({ open }) {
const menuStyle = [
'menu',
open && 'open'
].join(' ');
return (
<aside className={menuStyle}>
I am a sidebar
</aside>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
.menu { width: 100px; top: 0px; left: -120px; background-color: salmon; position: fixed; height: 100vh; padding: 10px; transition-property: left; transition-duration: 0.25s;}
.open { left: 0px; }
button { position: fixed; left: 150px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
getElementsByTagName() is method of Document or Element, not react event.
What you need to do, is to look for body inside document.
Also getElementsByTagName(), returns HTMLCollection (many elements), so you need to grab first one (usually there is only one body element on page)
document.getElementsByTagName('body')[0].classList.toggle('sb-sidenav-toggled');
There is also shortcut for body element document.body, so it can be also written as:
document.body.classList.toggle('sb-sidenav-toggled');

How can I manipulate the clicked button inside array map without affecting other ones?

I got API data and used it with the map method. Note that for each item of data, I have an AddButton component. I want to click the button that is inside map and change the text of the clicked one, without effecting the others, but this results in changing all buttons. How can I manipulate just the clicked one?
{data.map((values) =>
<>
<Items >
<h5>Title: {values.title}</h5>
<h2 >Price: {values.price}</h2>
<img src={values.image} alt=""
width="100"
height="60" />
<Addbutton onClick={() => {
Added();
}}>
</Addbutton>
</Items>
</>
)}
You need to pass the event, or at least the event target, so you can reference that.
{data.map(values =>
<Items>
...
<Addbutton onClick={event => Added(event.currentTarget)} />
</Items>
)}
Then your function accepts currentTarget as an element:
const Added = element => { // do stuff with element }
You need to be able to identify which item your button belongs to, and you can do that by adding an id property to the data. Here I've added it to the mock data I'm passing into the component.
On each iteration of map create a new Item passing down an id, a reference to the function that will be called when the button is clicked, and a new property called added. This will either be set to the item's property if it exists, or false.
When handleClick is called it makes a copy of the items state, looks for the object in the array with a matching id, adds the added property setting it to true, and then updates the items state. When that happens the component re-renders with the new state, and the button will change when the added property is true.
const { useState } = React;
function Example({ data }) {
const [items, setItems] = useState(data);
function handleClick(id) {
const copy = [...items];
const index = copy.findIndex(item => item.id === +id);
copy[index].added = true;
setItems(copy);
}
return (
<main>
{items.map(item => {
return (
<Item
key={item.id}
added={item.added || false}
handleClick={handleClick}
{...item}
/>
);
})}
</main>
);
}
function Item(props) {
const {
id,
added,
title,
price,
handleClick
} = props;
return (
<section className="item">
<p className="title">{title}</p>
<p className="price">{price}</p>
<Add
added={added}
id={id}
handleClick={handleClick}
/>
</section>
);
}
function Add({ added, id, handleClick }) {
return (
<button
className="add"
onClick={() => handleClick(id)}
disabled={added}
>{added ? 'Added item' : 'Add item'}
</button>
);
}
const data = [{id: 1, title: 'Car', price: 65000},{id: 2, title: 'Elephant', price: 620000},{id: 3, title: 'Wax Museum', price: 25000000}];
ReactDOM.render(
<Example data={data} />,
document.getElementById('react')
);
.item { background-color: #dcFecc; margin: 0.25em 0.4em; padding: 0.25em; border: 1px solid #dfdfdf; }
.title { font-weight: 600; color: blue; }
.price { color: red; }
.add:hover { cursor: pointer; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

REACT: Why after clicking on the button color of h1 is not changing? (but if you change color to i.e innerText then it works)

useEffect changes the css property but it takes no effect on the screen. But only so if I change color or bacgroundColor - innerText works just fine.
export function App(props) {
const [state1, setState1] = useState(true);
const hRef = useRef();
const clickHandler = () => {
setState1(!state1);
}
useEffect(()=>{
hRef.current.color=state1?'blue':'green';
setState1(state1);
},[state1]);
return (
<div className='App'>
<h1 ref={hRef}>Hello {`${state1}`}.</h1>
<button onClick={clickHandler}>Change State</button>
</div>
);
}
WRT to not needing to use ref here's an example that stores the "active" state of the button, and uses CSS classes to change the colour of the heading.
const { useState } = React;
// Accept a name prop
function App({ name }) {
// Initialise the state as false
const [ active, setActive ] = useState(false);
// The button updates the state
function handleClick() {
setActive(!active);
}
// We create class string which is determined by
// the current state
const cn = `heading ${active ? 'active' : ''}`;
// And then apply that string to the `className` prop
// of the heading
return (
<div className='App'>
<h1 className={cn}>Hello {name}.</h1>
<button
className="change"
onClick={handleClick}
>Change State
</button>
</div>
);
}
ReactDOM.render(
<App name="Wojtek" />,
document.getElementById('react')
);
.heading { padding: 0.3em; background-color: lightgreen; }
.active { background-color: lightblue; }
.change:hover { cursor: pointer; background-color: #ffffa0; border-radius: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Show and hide looped elements in ReactJs

I loop through an array of elements:
this.props.connections.map((connection) => (
For each element in this array a card is created. In this card, I implemented a toogle button:
<div id="bookmarkIcon">
{this.state.available ? (
<Tab onClick={this.handleChange} icon={<StarBorderIcon/>}
aria-label="StarBorder"/>) : <Tab onClick={this.handleChange} icon={<StarIcon/>}
aria-label="StarIcon"/>}
</div>
The handle change method changes the value of available to false. The problem is that then I change the state and therefore, ever icon toggles, but I just want to toggle the icon I clicked on. How can I achieve this?
You can create an object which keeps the state as keys.
Here is a working example:
hidden will look something like this {0: true, 1: true, 2: false}
so we can update the corresponding items by their index.
https://codesandbox.io/s/intelligent-black-83cqg?file=/src/App.js:0-577
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [hidden, setHidden] = useState({});
const list = ["aa", "bb", "cc", "dd"];
const toggleHide = index => {
setHidden({ ...hidden, [index]: !hidden[index] });
};
return (
<div className="App">
{list.map((item, index) => (
<div>
{!!hidden[index] && <span>[HIDDEN]</span>}
{!hidden[index] && <span>[VISIBLE]</span>}
{item} <span onClick={e => toggleHide(index)}>x</span>
</div>
))}
</div>
);
}
Class-Based Component
class PrivacyPolicyDetails extends Component {
constructor(props) {
super(props);
this.state ={
resultData:[],
error:false ,
active: false,
activeIdList:[]
}
this.toggleClass.bind(this);
}
componentDidMount() {
setting.getQuestionAnswerByType('privacy-policy')
.then(res =>{
if(res.data.questionAnswerList.length > 0){
this.setState({
resultData: res.data.questionAnswerList,
})
}else{
this.setState({
error:true
});
}
}
);
}
toggleClass(id) {
const currentState = this.state.active;
this.setState({ active: !currentState});
if(this.state.activeIdList.find(element => element == id)){
this.state.activeIdList = this.state.activeIdList.filter(item => item !== id);
}else{
this.state.activeIdList.push(id);
}
}
render() {
const { product, currency } = this.props;
const {resultData,error,activeIdList} = this.state;
return (
<div>
<h1>Privacy Policy</h1>
{resultData && resultData.length > 0 ? resultData.map(each_policy =>
<div className="item">
<div className="question"
onClick={() => this.toggleClass(each_policy.question_answer_repository_id)}
>
{each_policy.question}
</div>
<p className={(activeIdList.find(element => element == each_policy.question_answer_repository_id))? "active-div" :"hide"}>
<div className="answer">{each_policy.answer}</div>
</p>
</div>
):''}
</div>
);
}
}
const mapStateToProps = (state) => {
return state.setting;
};
export default connect(mapStateToProps)(PrivacyPolicyDetails);
css
.hide{
display: none;
overflow:hidden;
}
.active-div{
display: block;
overflow:hidden;
}
Make the card into its own component and implement the state of the toggle inside of that component. In your parent component just map each card into one of these components. Each card will have its own toggle which uses the state of the card to determine how it should display.

Handling selection of buttons + adding selected items to new array

I am working with a set of arrays which are printed to the screen as buttons via an API call.
I am looking to add my bases/frostings buttons (select one) and then add the key of each one selected to a new OrdersArray. I also need to be able to select multiple toppings (multi-select) and add those to a nested array within the OrdersArray.
I would like to also change the colors of each selected button when they are selected.
My Buttons function generates the buttons.
function Buttons({ list }) {
const style = {
display: 'inline-block',
textAlign: 'center',
border: '1px solid black',
padding: '10px',
margin: '10px',
width: '35%'
}
return (
<div>
{list && list.map(item =>
<button key={item.key}
style={style}
>
{/* <p>{item.key}</p> */}
<p>{item.name}</p>
<p>${item.price}.00</p>
{/* <p>{item.ingredients}</p> */}
</button>
)}
</div>
);
};
My app component renders the buttons.
Class App extends Component {
constructor() {
super();
this.state = {
'basesObject': {},
'frostingsObject': {},
'toppingsObject': {},
selectedButton: null
}
}
componentDidMount() {
this.getBases();
this.getFrostings();
this.getToppings();
}
/* GET DATA FROM SERVER */
getBases() {
fetch('http://localhost:4000/cupcakes/bases')
.then(results => results.json())
.then(results => this.setState({'basesObject': results}))
}
getFrostings() {
fetch('http://localhost:4000/cupcakes/frostings')
.then(results => results.json())
.then(results => this.setState({'frostingsObject': results}))
}
getToppings() {
fetch('http://localhost:4000/cupcakes/toppings')
.then(results => results.json())
.then(results => this.setState({'toppingsObject': results}))
}
render() {
let {basesObject, frostingsObject, toppingsObject} = this.state;
let {bases} = basesObject;
let {frostings} = frostingsObject;
let {toppings} = toppingsObject;
return (
<div>
<h1>Choose a base</h1>
<Buttons on
list={bases}
/>
<h1>Choose a frosting</h1>
<Buttons
list={frostings}
/>
<h1>Choose toppings</h1>
<Buttons
list={toppings}
/>
</div>
);
}
}
I'm new to React, any help would be appreciated! :)
You'll need to pass a function to the buttons that will modify a state value in the parent component when the button is clicked.
In parent:
const addToOrder = item => {
orderArray.push(item);
const newOrder = orderArray.reduce((acc, order) => {
return acc + " " + order.name;
}, "");
setOrder(newOrder);
};
...
<Button addToOrder={addToOrder} />
In Button.js
<button onClick={() => addToOrder(item)} >
Check out the whole thing in this Sandbox
For keeping track of which ones have been clicked you'll need to keep track of button state either in the button component itself or in the parent container if you want to keep the buttons stateless. Then set the button attribute disabled to true or false based on that state.
<button disabled={isButtonDisabled} />
Sorry I didn't have time to flesh the full thing out, but this should get you in the right direction.

Categories