How to toggle only one item in react? - javascript

I am making a collapsible section using reactstrap Collapse.
The data received are nested and I am in the need to display the collapse button at second level and the third level will have the data to be displayed.
No problem in this level of display and it already display the data at third level on click of button.
Problem: On click of any button, all the collapsible section gets opened instead of the clicked one.
Requirement: Only the clicked section's token(s) needs to get displayed and on click of the button again it should be collapsed back and it should not have any relation with any other items.
Working Snippet as follows,
const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
open: false
};
}
toggle = () =>
this.setState(s => ({
open: !s.open
}));
render() {
console.log();
return <div>
{
data.map((levelOneItem, i) => {
return(
<div>
<div> Order Id: {levelOneItem.orderId} </div>
{
levelOneItem.orderParts.map((levelTwoItem, j) => {
return(
<div>
<div> Order Part Id: {levelTwoItem.orderPartsId} </div>
<Button onClick={this.toggle}>Display Token</Button>
<Collapse isOpen={this.state.open}>
{
<div>
{levelTwoItem.regTokens.map((levelThreeItem, k) => {
return(<span> {levelThreeItem} </span>)
})
}
</div>
}
</Collapse>
</div>
)
})
}
</div>
)
})
}
</div>;
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>
Kindly help me to achieve the result of toggling only on the selected item for the nested data provided.

Add an identifying index as params for your handler function would be fine.
this.state = {
open: ''
};
toggle = idx => () => {
this.setState(prevState => ({open: prevState.open === idx ? '' : idx}));
}
<Button onClick={this.toggle(i)}>
<Collapse isOpen={open === i}>
Update
If you want them to be independent, you need an Array to store each status instead.
this.state = {
open: [0, 2] // Initial opened item's index
};
toggle = idx => () => {
this.setState(prevState => ({
open: this.state.open.includes(idx) // check whether been expanded
? prevState.open.filter(x => x !== idx) // if yes, remove from list
: [...prevState.open, idx]} // if no, add to list
))
}
<Button onClick={this.toggle(i)}>
<Collapse isOpen={open.includes(i)}>
const data = [{"orderId":1,"orderNo":"123", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3"]}] },
{"orderId":2,"orderNo":"456", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1","Token3"]}] },
{"orderId":3,"orderNo":"789", "orderParts":[{"orderPartsId":1,"orderPrtNo":"OP-1", "regTokens":["Token1", "Token2","Token3", "Token4"]}] }
]
const {Component, Fragment} = React;
const {Button, Collapse} = Reactstrap;
class Menu extends Component {
constructor(props) {
super(props);
this.state = {
open: [0, 2] // Initial opened item's index
};
}
toggle = idx => () => {
this.setState(prevState => ({open: this.state.open.includes(idx) ? prevState.open.filter(x => x !== idx) : [...prevState.open, idx]})
)}
render() {
const { open } = this.state;
return <div>
{
data.map((levelOneItem, i) => {
return(
<div>
<div> Order Id: {levelOneItem.orderId} </div>
{
levelOneItem.orderParts.map((levelTwoItem, j) => {
return(
<div>
<div> Order Part Id: {levelTwoItem.orderPartsId} </div>
<Button onClick={this.toggle(i)}>Display Token</Button>
<Collapse isOpen={open.includes(i)}>
{
<div>
{levelTwoItem.regTokens.map((levelThreeItem, k) => {
return(<span> {levelThreeItem} </span>)
})
}
</div>
}
</Collapse>
</div>
)
})
}
</div>
)
})
}
</div>;
}
}
ReactDOM.render(<Menu />, document.getElementById("root"));
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/reactstrap/8.4.1/reactstrap.min.js"></script>
<div id="root"></div>

Related

React: Having a button select something upon click a button

I currently have 4 buttons and 4 circles with 4 numbers each. When someone clicks button 1, circle 1 would be selected. When they click button 2, circle 2 would be selected. I currently have this:
class App extends Component {
constructor() {
super();
this.state = {
selButtonIdx: 1,
};
}
handleButtonSelection = (selButtonIdx) => {
this.setState({ selButtonIdx: selButtonIdx });
console.log(selButtonIdx);
};
render() {
return (
<div className="App">
<header className="App-header">Buttons</header>
<main>
<CircleSelector
selButtonIdx={this.state.selButtonIdx}
handleButtonSelection={this.handleButtonSelection}
/>
<Circles />
</main>
</div>
);
}
}
For my CircleSelector I have:
const CircleSelector = (props) => {
return (
<div className="CircleSelector">
{[1, 2, 3, 4].map((index) => (
<button
type="button"
onClick={() => props.handleButtonSelection(index)}
>
Select Button {index} {index === props.selButtonIdx && "(selected)"}
</button>
))}
</div>
);
};
and for circles:
const Circle = (props) => {
return (
<div className="Circles">
{[1, 2, 3, 4].map((index) => (
<div type="text">{index}</div>
))}
</div>
);
};
Yoou should integrate your circle and your button in the same component, this way you can simply use a reference to select the circle. I did the following example with input since i don't know what your circle contains but the idea is here :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
return (
<div>
{[1, 2, 3, 4].map(el => (
<MyComponent />
))}
</div>
);
};
const MyComponent = () => {
const elRef = React.createRef();
const selectElement = () => {
elRef.current.select(); // Select the content of the input
};
return (
<div>
<button onClick={selectElement}>Select element</button>
<input ref={elRef} type="text" value="element" />
</div>
);
};
render(<App />, document.getElementById("root"));
Here is the repro on stackblitz

how to Uncheck Checkbox.Group in Antd React.js

I have the following Code using https://ant.design/components/checkbox/, and Trying to uncheck when a checkbox has been checked.
I don't want to check all if button is click, just Uncheck all or the one checkbox selected.
constructor(props) {
super(props);
this.state = {
checked: true,
}
this.onChange = this.onChange.bind(this);
}
onChange(value) {
this.props.onChangeSession(value)
}
onChangeBox = e => {
this.setState({
checked: e.target.checked,
});
};
unChecked = () => {
this.props.onChangeSession([])
this.setState({
checked: false
});
};
render () {
const {
data,
} = this.props
return (
<div>
<Button type="primary" size="small" onClick={this.unChecked}>
Uncheck
</Button>
<Checkbox.Group
style={{ width: '100%' }}
onChange={this.onChange}>
<div>
<div className="filter-subhead">Track</div>
{data.map(i =>
<div className="filter-item">
<Checkbox
checked={this.state.checked}
onChange={this.onChangeBox}
value={i}>{i}</Checkbox>
</div>
)}
</div>
</Checkbox.Group>
</div>
)
}
Any help will be appreciate!
Working Link
The toggle on the checkbox wasn't working due to Checkbox.Group, you can simply use Checkbox
Regarding Checkbox State:
You can't have a single state for all the checkboxes, so you will need to have an array of bool which will serve as the state for each checkbox item.
In the example, I have initialized checkbox state on componentDidMount and it creates an array ([false,false,false,...]) and the exact same thing is used for resetting on Uncheck. (Possibility of refactoring in my code)
User assigned state will decide whether to check the checkbox or not.
import React from "react";
import ReactDOM from "react-dom";
import { Button, Checkbox } from "antd";
import "antd/dist/antd.css";
import "./index.css";
let data = [3423, 3231, 334234, 55345, 65446, 45237];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxArray: []
};
// this.onChange = this.onChange.bind(this);
}
componentDidMount() {
let createEmptyArray = new Array(data.length).fill(false);
this.setState({ checkboxArray: createEmptyArray });
}
onChange(e) {
console.log(e.target);
}
onChangeBox = (e, i) => {
let checkBoxCurrentState = this.state.checkboxArray;
checkBoxCurrentState[i] = !checkBoxCurrentState[i];
this.setState({
checkboxArray: checkBoxCurrentState
});
};
unChecked = e => {
let resetArray = new Array(data.length).fill(false);
this.setState({
checkboxArray: resetArray
});
};
render() {
const { data } = this.props;
return (
<div>
<Button type="primary" size="small" onClick={this.unChecked}>
Uncheck
</Button>
<div>
<div className="filter-subhead">Track</div>
{data.map((i, index) => (
<div className="filter-item">
<Checkbox
checked={this.state.checkboxArray[index] ? true : false}
onChange={e => this.onChangeBox(e, index)}
value={index}
>
{JSON.stringify(this.state.checkboxArray[index])}
</Checkbox>
</div>
))}
</div>
{JSON.stringify(this.state.checkboxArray)}
</div>
);
}
}
ReactDOM.render(<App data={data} />, document.getElementById("root"));
Simple copy and paste the above code and add props where required.
And if you want to user Checkbox.Group, will need to update the onChange method of CheckBox.Group
let data = ['Apple', 'Pancakes', 'Butter', 'Tea', 'Coffee'];
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
checkboxArray: []
};
// this.onChange = this.onChange.bind(this);
}
componentDidMount() {
let createEmptyArray = new Array(this.props.data.length).fill(false);
this.setState({ checkboxArray: createEmptyArray });
}
onChangeBox = (e, i) => {
let checkBoxCurrentState = this.state.checkboxArray;
checkBoxCurrentState[i] = !checkBoxCurrentState[i];
this.setState({
checkboxArray: checkBoxCurrentState
});
};
unChecked = () => {
let resetArray = new Array(data.length).fill(false);
this.setState({
checkboxArray: resetArray
});
};
render() {
const { data } = this.props;
return (
<div>
<button onClick={this.unChecked}>Clear All</button>
{this.props.data.map((single, index) => (
<div>
<input
type="checkbox"
id={index}
name="scales"
checked={this.state.checkboxArray[index]}
onChange={e => this.onChangeBox(e, index)}
/>
<label for={index}>{single}</label>
</div>
))}
</div>
);
}
}
ReactDOM.render(<App data={data} />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If you're going to use a map to dynamically generate the checkboxes, using state to keep track of checked value will be tricky. You should not do this.
What you should do is not include the checked prop at all in the component.
<Checkbox
onChange={this.onChangeBox}
value={i}>{i}
</Checkbox>
The checkbox should still check even if you do not include the checked prop. It's a little strange, I know.
Instead just pass the value into the onChangeBox function and handle all your logic there and set a state value on change.
I just tested it out and it works.

Toggle one list item instead of entire list

I am making a card that lists radio stations and when you click on a station a picture drops down for that list item. all data i am pulling from a json.
I have tried building the list in the toggle.js and in the app.js files
app.js-- div with toggle (its all inside a card..many divs)
<div class="card-trip-infos">
<div>
<Toggle />
</div>
<img class="card-trip-user avatar-bordered"/>
</div>
toggle.js render block:
state = {
on: false,
}
toggle = () => {
this.setState({
on: !this.state.on
})
}
render() {
return (
<ul>
<div>
<p>{PostData.radios[0].name}</p>
{PostData.radios.map((postDetail, index) => {
return <div>
<li onClick={this.toggle}>
<span id='radioName'>{postDetail.name}</span> <span id='frequency'>{postDetail.frequency}</span>
</li>
{
this.state.on && <img src='imagesrc'></img>
}
</div>
})}
</div>
</ul>
)
}
}
I dont know what exactly is wrong but i expect to be able to toggle a picture for each list row seperately. i am confused where to iterate over my json and where to plug everything in.
many Thanks!!
Since we don't know your toggle function and all your component we can't make exact suggestions but in order to do what you want here (just toggle the selected item), you have two different approaches.
You can keep the selected state in the component itself.
class App extends React.Component {
state = {
cards: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
],
};
render() {
const { cards } = this.state;
return (
<div>
{cards.map(card => (
<Card
key={card.id}
card={card}
/>
))}
</div>
);
}
}
class Card extends React.Component {
state = {
selected: false
};
handleSelect = () =>
this.setState(state => ({
selected: !state.selected
}));
render() {
const { card } = this.props;
return (
<div
className={this.state.selected ? "selected" : ""}
onClick={this.handleSelect}
>
{card.id}-{card.name}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
.selected {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
You can keep the selected state in the parent component.
class App extends React.Component {
state = {
cards: [
{ id: 1, name: "foo" },
{ id: 2, name: "bar" },
{ id: 3, name: "baz" }
],
selected: {}
};
handleSelect = id =>
this.setState(state => ({
selected: { ...state.selected, [id]: !state.selected[id] }
}));
render() {
const { cards, selected } = this.state;
return (
<div>
{cards.map(card => (
<Card
key={card.id}
card={card}
handleSelect={this.handleSelect}
selected={selected[card.id]}
/>
))}
</div>
);
}
}
const Card = ({ card, handleSelect, selected }) => {
const handleClick = () => handleSelect(card.id);
return (
<div className={selected ? "selected" : ""} onClick={handleClick}>
{card.id}-{card.name}
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
.selected {
background-color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />
Create a component for individual list item, have a state there which will hold a boolean property to tell whether the image is to be shown or not. Render function will look something like this:
render (
return (
<li onClick="this.toggleShowImage()">
<div> {props.data.name} </div>
<div> {props.data.frequency} </div>
{this.state.showImage ? <img src='imageUrl' /> : null }
</li>
)
)
Then in toggle.js iterate through your data and create the list components. The render function will look something like below:
render() {
return (
<ul>
{PostData.radios.map((postDetail, index) => {
return <ListItem key={postDetail.name} data={postDetail} />
})}
</ul>
)
}

How to hide all the div while showing the one which is clicked, in React?

I'm generating some identical div through a list. Below is the sample code.
I'm toggling this div
class App extends React.Component {
state = { showKitten: false };
handleToggleKitten = () => {
this.setState((prevState, props) => ({
showKitten: !prevState.showKitten,
}));
};
render() {
return (
<About
data={datalist}
showKitten={this.state.showKitten}
handleToggleKitten={this.handleToggleKitten}
/>
);
}
}
const About = ({ datalist, showKitten, handletogglekitten }) => {
return (
<div>
{datalist.map((item, index) => {
return (
<div key={index}>
<div onClick={handletogglekitten} />
showKitten ? <div /> : null
</div>
);
})}
</div>
);
};
I have defined tooglefunction and the flag state variable in parent and passing them to children and in children component, I'm creating this divs by iterating over a list. Right now I am able to achieve the toggle functionality for the individual div set but I want to hide all the div and show the one which is clicked.
You could use the index value. Here's a working example.
const datalist = ["cat 1", "cat 2", "cat 3"];
class App extends React.Component {
state = { showKittenIndex: null };
render() {
return (
<About
datalist={datalist}
showKittenIndex={this.state.showKittenIndex}
toggleKitten={index => this.setState({ showKittenIndex: index })}
/>
);
}
}
const About = ({ datalist, showKittenIndex, toggleKitten }) => (
<div className="about">
{datalist.map((item, index) => (
<div key={index}>
<button onClick={() => toggleKitten(index)}>toggle {index}</button>
{showKittenIndex === index && <div>{item}</div>}
</div>
))}
</div>
);
I have a very similar approach than #Kunukn.
But I don't see the need to wrap it in a functional component.
import React, { Component } from 'react';
const elements = ['DIV #1', 'DIV #2', 'DIV #3', 'DIV #4', 'DIV #5', 'DIV #6'];
class App extends Component {
constructor(props) {
super(props);
this.state = {
activeElement: null,
allElements: elements,
};
}
render() {
return (
<About
elements={this.state.allElements}
showIndex={this.state.activeElement}
toggleIndex={index => this.setState({ activeElement: index })}
/>
);
}
}
const About = ({ elements, showIndex, toggleIndex }) => (
<div className="about">
{elements.map((element, index) => (
<div key={index}>
<div onClick={() => toggleIndex(index)}>toggleIndex {index}</div>
{showIndex === index && <div>{element}</div>}
</div>
))}
</div>
);
export default App;
I did write a little clickHandler ... I know that it is not needed at the moment, but when you would want to alter the data received with the click-event this could be handled there as well.
EDIT
According to the comment I improved the code a bit by making a functional component showing the DIVs. I did also dismiss the clickHandler() function.

defaultValue is pulling the last value in React

If i click on a particular ToDos edit button, its value should be defaulted inside the textarea but everytime the last ToDo is defaulting, can somebody please help, whether using ref is a right choice or something else, then where i m wrong what i'm suppose to do ?
handleEdit() {
e.preventDefault();
.....
}
renderDisplay() {
return(
<div>
{
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
<div>
<button className="btn btn-primary btn-xs glyphicon glyphicon-pencil"
onClick={this.handleEdit.bind(this)}
/>
</div>
<hr/>
</li>
</div>
)})
}
</div>
);
}
renderForm() {
return(
<div>
<textarea className="form-control" defaultValue={this.refs.newText.innerText} rows="1" cols="100" style={{width: 500}}/>
</div>
)
}
render() {
if(this.state.editing) {
return this.renderForm();
}else {
return this.renderDisplay();
}
}
}
First of all you are using an old ref API. You should use this one, where you set the ref to the instance of the class using this with a callback.
<input ref={ref => {this.myInput = ref}} />
And then you can access its value by just referring to this.myInput .
As for your "bug", keep in mind that you are looping over and overriding the ref. so the last ref assignment would be the last item in the array.
this.props.listArr.map((list,i) => {
return(
<div key={i} index={i} ref="newText">
<li>{list}
There will always be 1 newText ref and it will always be the last item in the array.
You should render different ref names according to the item id and then pass the id of the item to the renderForm so it can access the relevant ref.
With that said, i really recommend to extract the todo to a different component as well as the form. I don't see a valid reason to use refs in this case.
Edit
As a follow-up to your comment, here is a small example of how you would use components instead of refs in order to get information from the child like values etc..
class Todo extends React.Component {
onClick = () => {
const { todoId, onClick } = this.props;
onClick(todoId);
}
render() {
const { value, complete } = this.props;
return (
<div
style={{ textDecoration: complete && 'line-through' }}
onClick={this.onClick}
>
{value}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: [
{ id: '1', value: 'to do this', complete: false },
{ id: '2', value: 'todo that', complete: true },
{ id: '3', value: 'will do it later', complete: false }]
}
}
toggleTodo = (todoId) => {
const { todos } = this.state;
const nextState = todos.map(todo => {
if (todo.id !== todoId) return todo;
return {
...todo,
complete: !todo.complete
}
});
this.setState({ todos: nextState });
}
render() {
const { todos } = this.state;
return (
<div >
{
todos.map((todo) => {
return (
<Todo
complete={todo.complete}
key={todo.id}
todoId={todo.id}
value={todo.value}
onClick={this.toggleTodo}
/>
)
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>

Categories

Resources