How to toggle CSS class between buttons rendered with .map() - javascript

I'm trying to change the color of a button from gray to blue when it gets clicked, and also change the color of the previously selected button back to gray, so only the current selected button is blue. However, the buttons are generated using the map() method, so with useState I get all buttons to change color at the same time:
codesandbox
import React, {useState } from "react";
import "./styles.css";
export default function App() {
const menuItems = ["day 1", "day 2", "day 3"]
const [active, setActive] = useState(false);
return (
<nav>
{menuItems.map((days, idx) => {
return (
<button
key={days}
onClick={() => {
setActive(!active)
}}
style={{backgroundColor: active? "blue" : ""}}
>
{days}
</button>
);
})}
</nav>
);
};

A possible solution is to store the currently selected button's name (or some Id) in the state.
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const menuItems = ["day 1", "day 2", "day 3"];
const [activeButton, setActiveButton] = useState('');
return (
<nav>
{menuItems.map((days, idx) => {
return (
<button
key={days}
onClick={() => {
setActiveButton(days);
}}
style={{ backgroundColor: activeButton === days ? "blue" : "" }}
>
{days}
</button>
);
})}
</nav>
);
}
Make sure your buttons have different names (as they are already now) for this to work and also because you're passing them in the key prop. If you think more than one button could have the same names in the future, add an id field too.
const menuItems = [{id: 1, name: "day 1"}, {id: 2, name: "day 2"}, {id: 3, name: "day 3"}];
Update the code accordingly. It won't be a good idea to pass array index in the key prop, if the situation changes. So, the id field will come in handy.

You can change your active state from a bool to a number and set it to the index of the selected button
const [active, setActive] = useState(null);
And change your click event
onClick={() => {
setActive(idx)
}}
And change your class condition to this
style={{backgroundColor: active === idx ? "blue" : ""}}

You should do this way:
export default function App() {
const menuItems = ["day 1", "day 2", "day 3"];
const [active, setActive] = useState(0);
return (
<nav>
{menuItems.map((days, idx) => {
return (
<button
key={days}
onClick={() => {
setActive(idx);
}}
style={{ backgroundColor: idx===active ? "blue" : "" }}
>
{days}
</button>
);
})}
</nav>
);
}
In this case, you selected index key

Currently, the state active will get toggled no matter which button you're firing, so you'll be changing the state that every button depends on.
One of the ways for you to solve this problem would be for you to create a button component that has the state action, by doing this each state will belong to the instance of the component.
Once you created your component, map the component instead and pass in the props you need
export default function App() {
const menuItems = ["day 1", "day 2", "day 3"];
return menuItems.map((day, idx) => <Button key={idx} day={day} />);
}
function Button({ day }) {
const [active, setActive] = useState(false);
return (
<button
style={{ backgroundColor: active ? "blue" : "" }}
onClick={() => setActive(!active)}
>
{day}
</button>
);
}

Related

How to independently set a button click in react?

I am trying to figure out on how to set a button that can independently be disabled when using the .map function. So I created a state whenever the button is clicked, it disables that specific button. However, it only disables all of the buttons which is not what i wanted. Is there a way to make the buttons have their own click event which can be disabled according to the index?
const [click, setClick] = useState(false);
const array = ["item 1", "item 2", "item 3"];
const disableClick = () => {
setClick(true);
};
return (
<div className="App">
{array.map((item) => (
<div>
<h2>{item}</h2>
{!click ? (
<button onClick={disableClick}>CLICK {item}</button>
) : (
<button disabled>Button clicked</button>
)}
</div>
))}
</div>
);
Move the click handler and useState in a separate component.
const ButtonView = (textContent) => {
const [disabled, setDisabled] = useState(false);
const onClick = () => {
setDisabled(true);
};
if (disabled) {
return <button disabled={disabled}>Button clicked</button>;
}
return <button onClick={onClick}>CLICK {textContent}</button>;
};
export const View = () => {
const array = ["item 1", "item 2", "item 3"];
return (
<div className="App">
{array.map((item, key) => (
<div key={key}>
<h2>{item}</h2>
<ButtonView textContent={item} />
</div>
))}
</div>
);
};
Or do this:
const ButtonView = (textContent) => {
const [disabled, setDisabled] = useState(false);
const onClick = () => {
setDisabled(true);
};
const content = disabled ? "Button clicked" : textContent
return <button disabled={disabled} onClick={onClick}>CLICK {content}</button>;
};
Disable takes an arguments you can supply it with
<button disabled={click? true:false} >Button clicked</button>
Is there a way to make the buttons have their own click event which
can be disabled according to the index?
Yes, How? instead of setting the click state to true or false, we can set it to contain name of the button const [disabledBtn, setDisabledBtn] = useState("item 1"); and then we can compare it with the items from the map.
<button disabled={disabledBtn == item ? true:false} >Button clicked</button>
This code render the buttons with the first button disabled by default, if the user clicks on one of the other available buttons it will trigger rerender and that btn will be disabled.
You should add a key to your childern components to maintain uniqueness between renderings.
This code snippet should answer your question
const [disabledBtn, setDisabledBtn] = useState("item 1");
const array = ["item 1", "item 2", "item 3"];
const handleDisableClick = (item) => {
setDisabledBtn(item);
};
return (
<div className="App">
{array.map((item, idx) => (
<div key={idx}>
<h2>{item}</h2>
<button
disabled={disabledBtn == item ? true : false}
onClick={() => handleDisableClick(item)}
>
Button clicked
</button>
</div>
))}
</div>
);

How to add rows dynamically in ReactJS?

I'm still beginner to ReactJS and need to build a dynamic table for my work.
In that table, the user can add new lines and can also remove any existing lines.
The problem is, I don't know how to save the values that are typed into the new fields. My onChange function isn't working, I've done several tests, but I'm not able to save the entered values.
Here's my code I put into codesandbox.
Could you tell me what I'm doing wrong to save the entered values? Thank you in advance.
import React from "react";
import "./styles.css";
import List from "./List/List";
const App = () => {
const [data, setData] = React.useState([
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
const handleOnChange = (field) => (e) => {
setData((prev) => ({
...prev,
[field]: e.target.value
}));
};
const addRow = () => {
setData([
...data,
[
{
label: "Property Name",
field: "propertyName",
value: ""
},
{
label: "Value",
field: "value",
value: ""
}
]
]);
};
const removeRow = (index) => {
const _data = [...data];
_data.splice(index, 1);
setData(_data);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<List
data={data}
addRow={addRow}
removeRow={removeRow}
handleOnChange={handleOnChange}
/>
</div>
);
};
export default App;
import React from "react";
import AddCircleIcon from "#material-ui/icons/AddCircle";
import RemoveCircleIcon from "#material-ui/icons/RemoveCircle";
import TextField from "#material-ui/core/TextField";
import "./styles.scss";
const List = ({ data, handleOnChange, addRow, removeRow }) => {
return (
<div className="container">
{data.map((items, index) => (
<div key={index} className="content">
<div className="content-row">
{items.map((item, index) => (
<TextField
key={index}
label={item.label}
value={item.value}
onChange={handleOnChange(index)}
variant="outlined"
/>
))}
</div>
<div>
<AddCircleIcon onClick={addRow} />
{data.length > 1 && (
<RemoveCircleIcon onClick={() => removeRow(index)} />
)}
</div>
</div>
))}
</div>
);
};
export default List;
Good that you shared the code. There are several issues with your code. I have an updated code placed under this URL,
https://codesandbox.io/s/reverent-mclean-hfyzs
Below are the problems,
Your data structure is an array of arrays and your onchange event doesn't respect that.
You have no property available [name/id] to identify a textbox when you change the value.
I had to add a name property to each textbox and design it like a 2D array so any textbox will have a unique name.
I had to map through the data array and find the node where I have to update the value and set the new value as the new state when any textbox changes.
I have added a console.log while adding a row so you can see the current state.

React list, show active item onClick. Without re-rendering the list

I want to show different style for the stock item which is clicked, but when i set activeItem to idx clicked, react re-renders the whole list again. Is there any way to show only one active item without re-rendering the list
import React, { useState,memo} from "react";
const StocksList=({stocksData})=>{
const [activeItem, setActiveItem] = useState(0);
const Item=({stock,idx})=>{
return (
<li
className={activeItem===idx ? "active-stock-item" : "stock-item" }
onClick={()=>{
setActiveItem(idx);
}}
>
{`${idx+1}. ${stock["Name"]}`}
</li>
)
}
return(
stocksData.map((stock,idx) => <Item key={stock._id} {...{stock,idx}} />)
)
}
export default memo(StocksList)
Set a key on each rendered item.
stocksData.map((stock,idx) => <Item key={idx} {...{stock,idx}} />)
As a side-note, it's recommended not to use the index of the object as the key. If stock has an ID or some other unique identifier, use that.
Edit
Based on your comments, here's a working version that'll update select the active item:
import React, { useState,memo} from "react";
const stocksData = [
{
_id: 0,
name: "Zero"
},
{
_id: 1,
name: "One"
}
];
const Item=({ _id, name, active, onClick })=>{
function onSelfClick() {
onClick(_id);
}
return (
<li
className={active ? "active-stock-item" : "stock-item" }
onClick={onSelfClick}
>
{`${ _id +1 }. ${name}. ${active}`}
</li>
)
}
export default function StocksList() {
const [activeItem, setActiveItem] = useState(stocksData[0]._id);
function onClick(id) {
setActiveItem(id)
}
return (
stocksData.map(stock => <Item key={stock._id} active={activeItem === stock._id} onClick={onClick} {...stock} />)
)
}

Is there a way to iterate over a list of buttons & toggle a specific button when it is clicked?

Issue - When I click one button to display answer text, the other button opens too. I only want the answer for each question to be opened when I click on it.
My Code -
import React, { useState } from "react"
const Questions = [
{
question: "What is your favourite colour?",
answer: "My favourite colour is pink",
},
{
question: "What is your favourite animal?",
answer: "My favourite animal is a monkey",
},
]
const FAQs = () => {
const [isOpen, setIsOpen] = useState(false)
const toggle = () => setIsOpen(!isOpen)
return (
<div>
{Questions.map((element, i, array) => {
return (
<div>
<button onClick={toggle} key={i}>
{element.question}
</button>
{isOpen && <p key={i}>{element.answer}</p>}
</div>
)
})}
</div>
)
}
export default FAQs
Make your isOpen to an object, then assign the boolean value as isOpen[index]=false|true as required. And in the HTML, bind as isOpen[i] &&...
The poblem what you are doing here is that the variable isOpen is not related to your Questions objects.
You can change isOpen to an object and set it like this.
const [isOpen, setIsOpen] = useState({})
const toggle = (i) => setIsOpen({...isOpen, [i]:!isOpen[i]})
Then use it like this
<button onClick={() => toggle(i)} key={i}>
{element.question}
</button>
{isOpen[i] && <p key={i}>{element.answer}</p>}

Is there a way to add an active class onClick for an array of buttons?

I map through the buttons array and get three buttons. What i want to achieve is to be able to identify which button is clicked and add an active class
import React from "react";
import { useState } from "react";
import style from "./years.module.scss";
const Years = props => {
const [activeClass, setActiveClass] = useState(null);
const buttons = ["year one", "year two", "year three"];
return (
<div className={style.box}>
{buttons.map((button, index) => {
console.log(index,button)
return (
<button key={index} className={`${style.btn} ${style.active}`} >
{button}
</button>
);
})}
</div>
);
};
export default Years;
I use css modules to style the buttons
Just connect handler with each button - if clicked, the state is changed and stores the index of active button. If it's active - add class.
const changeActiveButton = (i) => () => {
setActiveClass(i);
};
<button
onClick={changeActiveButton (index)}
className={`${style.btn} ${index === activeClass ? style.active : ''}`}
>

Categories