So I have an array of objects. I iterate through this array and create a button for each object.
When a button is pressed that object of the button pressed has a value "active" that will be set to true. when another button is pressed its "active" value is now true all all the other ones are turned to false.
it looks like this
myarray.map(item =>
<Button
className={item.active? "btn-active" : "btn-disabled"}
onClick={() => setActive(item);
}}
>
{item.active? "Checking..." : "Start"}
</Button>
)
The behavior I expect is when a button is pressed it turns to action, and all the rest remain inactive, when a new button is pressed the new button is now active and all the rest are disabled. only one active button at a time.
However, the issue I am having is when a new button is pressed it turns to active, but the old one does not change class and stays active also even though it "active" property is set to false.
Any idea how can I fix this behavior?
Without a full picture of how you are using state, here is a working example. Another issue I seen is that you are missing a key on your mapped jsx element.
It's possible you are not mutating myarray statefully.
import "./styles.css";
import React from "react";
export default function App() {
const [myarray, setMyarray] = React.useState([
{ id: 1, active: false },
{ id: 2, active: false }
]);
const setActive = (id) => {
setMyarray((prev) =>
prev.map((item) => {
if (item.id === id) {
return { ...item, active: true };
}
return { ...item, active: false };
})
);
};
return (
<div className="App">
{myarray.map((item) => (
<button
key={`button-${item.id}`}
className={item.active ? "btn-active" : "btn-disabled"}
onClick={() => setActive(item.id)}
>
{item.active ? "Checking..." : "Start"}
</button>
))}
</div>
);
}
https://codesandbox.io/s/flamboyant-shirley-i24v0z
Related
Hello I am making a tab menu right now.
My problem is that I want to give is-active when the button is clicked, but I don't know what value to put in the empty space there.
type Props = {
title: string
index: number
setSelectedTab: (index: number) => void
}
const TabTitle: React.FunctionComponent<Props> = ({ title, setSelectedTab, index }) => {
// The value to be activated. Initial value is `0th button`
const [activeIndex, setActiveIndex] = useState(0);
const onClick = useCallback(() => {
setSelectedTab(index)
}, [setSelectedTab, index])
return (
<li>
<button
key={index}
className={activeIndex === [empty place] ? "is-active" : ""}
onClick={() => onClick()}
>
{title}
</button>
</li>
)
}
console.log(index);
// 0
// 1
// 2
How to use the index value If you click the 0th button, index : 0
If you click the 1st button, index: 1
After making it like this, className={activeIndex === index ? "is-active" : ""} If you put index , it will work normally, but I don't know how to make an index like that.
How can I set the index to give first click 0 second click 1 according to the clicked value?
You need to compare the index property to the selectedTab property (which presumably exists on the parent component, since you pass setSelectedTab function to the TabTitle component).
import { useCallback } from "react";
type Props = {
title: string;
index: number;
selectedTab: number; // <-- this should be passed by the parent
setSelectedTab: (index: number) => void;
};
const TabTitle: React.FunctionComponent<Props> = ({
title,
selectedTab,
setSelectedTab,
index
}) => {
const onClick = useCallback(
(index) => {
setSelectedTab(index);
},
[setSelectedTab]
);
return (
<li>
<button
key={index}
className={selectedTab === index ? "is-active" : ""} // it's active if its index is the same as the selected tab
onClick={() => onClick(index)} // <-- so it knows which button was clicked
>
{title}
</button>
</li>
);
};
export default TabTitle;
You can see a simple example on codesandbox.
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);
}
I have mapped list of data from JSON. When I clicked on of the item it should open a crawl with additional details from the same JSON file. I am able to map everything one I clicked bit I was not able to toggle. How do I do toggling.
This is my render method
render() {
return (
<div>
<h1>API</h1>
<div>
{this.state.apis.map(api => (
<div
key={api.id}
id={api.id}
onClick={this.handleCrawl}>
{api.title}
</div>
))}
</div>
<div>
{this.state.apis.map(api => (
<div
key={api.id}
id={api.id}>
{this.state.showCrawl[api.id] && (
<SwaggerUI url={api.opening_crawl}/>
)}
</div>
))}
</div>
</div>
);
}
This is the method for toggling. When I clicked an item the SwaggerUI component shows up and If I clicked the same link it hides.
The problem is if I clicked the 2nd link 1st link still shows. I need other view to be closed.
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
just don't spread the previous state's props.
try this:
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { [id]: !current.showCrawl[id] }
}));
};
Because in your code:
initial state:
{showCrawl: {}}
Say first time you click the first one(id: 1), your state become:
{showCrawl: {1: true}}
then u click the second one(id: 2)
{showCrawl: {1: true, 2: true}}
That's not your expected. Right?
So just don't spread the property, it should be going well.
In general, you can show or hide an element in a react component like this:
{this.state.showComponent ? (<Component/>) : (null)}
as an alternative, you can control the hiding/showing of the element in the component itself, with a show prop:
<Component show={this.state.showComponent} />
-- edit
I think I misunderstood your problem. Your problem is that you only want SwaggerUI to show for one thing at a time, but it's showing for multiple.
This is because of the way you designed your function,
handleCrawl = e => {
const { id } = e.target;
this.setState(current => ({
showCrawl: { ...current.showCrawl, [id]: !current.showCrawl[id] }
}));
};
You're only ever ADDING ids to showCrawl, not changing the ids that you toggled previously. You'll have to fix that function
I have a button that changes the active state onClick:
render() {
return(
<SomeButton
onClick={e => this.handleClick(e)}
id={someId}
activeStatus={someId === this.state.active ? "active" : "not active"}
/>
)
}
The function that changes the state:
handleClick(e) {
e.preventDefault();
this.setState({ active: e.currentTarget.id });
}
The state:
this.state = {
active: null
};
The button that receives the activeStatus props:
export default function SomeButton({ activeStatus }) {
console.log(activeStatus);
return (
// button jsx code
);
}
However, every time I click on the button (I have 3 instances of that button on the page), the activeStatus console.log shows:
I click on button 1:
active
not active
not active
I click on button 2:
active
active
not active
I click on button 3:
active
active
active
I was expecting that the status would toggle depending on the active button which is clicked.
What am I missing?
You can set the state in an array:
this.state = {
active: [false, false, false] // or just: []
};
handleClick(e) {
e.preventDefault();
const activeState = [false, false, false]; // or just: []
activeState[e.currentTarget.index] = true;
// button index ^^
this.setState({ active: activeState });
}
And just pass the activeStatus to the active state:
activeStatus={this.state.active}
Inside your component, bind the active state:
<button className={ activeStatus[0] ? 'active' : 'not-active' }>...</button>
<button className={ activeStatus[1] ? 'active' : 'not-active' }>...</button>
<button className={ activeStatus[2] ? 'active' : 'not-active' }>...</button>
I'd use e.target.id instead of e.currentTarget.id and if the button ids are static, then you could put them into your state and use the id to update a buttonState object (one of several ways to handle it).
Working example: https://codesandbox.io/s/olmn9k08m5
Some notes:
Keep your state consistent (if it's a string, keep it a string, if
it's an array, keep it any array...etc -- in the example below
buttonState is an object and stays an object).
Also, you don't need e.preventDefault() unless you're submitting a
form or trying to block functionality.
Always specify the button's type ("button" or "submit")
ShowButton.js
import React, { Component } from "react";
import SomeButton from "./SomeButton";
const buttons = ["button1", "button2", "button3"];
export default class App extends Component {
state = {
buttonState: {
button1: "inactive",
button2: "inactive",
button3: "inactive"
}
};
handleClick = e => {
const { id } = e.target; // id="button1","button2" or "button3"
this.setState(prevState => ({
buttonState: {
...prevState.buttonState, // spread out object
[id]: prevState.buttonState[id] === "active" ? "inactive" : "active" // use the [id] as an object property (ex: "button1") and set the property's value to "active" or "inactive"
}
}));
};
render = () => (
<div className="container">
<h1>Controlling Active Buttons</h1>
{buttons.map(id => (
<SomeButton
key={id}
id={id}
handleClick={this.handleClick}
activeStatus={this.state.buttonState[id]}
/>
))}
</div>
);
}
SomeButton.js
import React from "react";
export default ({ activeStatus, handleClick, id }) => (
<div style={{ marginBottom: 20 }}>
<button
type="button"
style={{ minWidth: 150 }}
className={`uk-button ${
activeStatus === "active" ? "uk-button-primary" : null
}`}
id={id}
onClick={handleClick}
>
{activeStatus}
</button>
</div>
);
I have a basic component that looks as follows.
class List extends React.Component {
constructor() {
super(...arguments);
this.state = {
selected: null,
entities: new Map([
[0, { 'name': 'kot'} ],
[1, { 'name': 'blini'} ]
])
};
}
render() {
return (<div>
<ul>{this.renderItems()}</ul>
</div>)
}
renderItems() {
return Array.from(this.state.entities.entries()).map(s => {
const [ id, entry ] = s;
return <li
key={id}
onClick={() => this.setState(state => ({ selected: id }))}
style={{
color: id === this.state.selected ? 'red' : 'black'
}}
>{entry.name}</li>
})
}
}
This works in order to allow me to click on any element and select it. A selected element will appear red. codepen for easy editing.
However, I want behavior that will unset any currently selected item if a click event was found that was not one of these <li> elements.
How can this be done in React?
In your List component, You can add
componentDidMount() {
window.addEventListener("click", (e) => {
let withinListItems = ReactDOM.findDOMNode(this).contains(e.target);
if ( ! withinListItems ) {
this.setState({ selected: null });
}
});
}
And in your renderItems, change onClick to
onClick={ (e) => {
// e.stopPropagation();
this.setState({ selected: id });
}
}
You can checkout this codepen http://codepen.io/anon/pen/LRkzWd
Edit:
What #kubajz said is true, and hence i have updated the answer.
Random User's answer is correct, it may have one flaw - it relies on stopPropagation and there is possibility that some piece of code may no longer work as expected - imagine collecting user's behaviour on page and sending metrics somewhere - stopPropagation will prevent bubbling, thus click is not recorded. Alternative approach is to check what was clicked in event.target: http://codepen.io/jaroslav-kubicek/pen/ORXxkL
Also there is nice utility component for listening on document level: react-event-listener