React: Having a button select something upon click a button - javascript

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

Related

React when I update state on one element all parent element and their parents functions are called, trying to understand React re-rendering?

I've created a very simplified code version of my problem to understand the REACT rendering using typescript. When I click a button which changes state in the lowest child element all parent elements are updated by the renderer and their children on other forks. How can I change the below so it doesn't do that.
import * as React from 'react';
import { connect } from 'react-redux';
import './Grid.css';
const RenderPopup = (key: number) => {
const open = () => setShowDialog(true);
const [showDialog, setShowDialog] = React.useState(false);
const close = () => setShowDialog(false);
if (!showDialog) {
return (
<div>
<button onClick={open}>do it</button>
</div>
)
}
else {
return (
<div>
<button onClick={close}>close
</button>
</div>
)
}
}
function Cell(key:number) {
return (
<div key={key}>
{key}
{RenderPopup(key)}
</div>
)
}
const Header = () => {
return (
<div className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Person = (rowNum: number) => {
return (
<div key={rowNum} className="gridRow">
{Cell(0)}
{Cell(1)}
{Cell(2)}
</div>
)
}
const Persons = () => {
return (
<div>
{Person(1)}
{Person(2)}
{Person(3)}
</div>
)
}
const Grid = () => {
return (
<div>
<Header />
<Persons />
</div>
);
}
export default connect()(Grid);

How to toggle only one item in react?

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>

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>
)
}

Unable to select an item from select box / write in the input field. Am I handling 'key' incorrectly?

I was writing a component with the code given as follows, which after rendering looks like:
I have used antd components to render the fields. The problem that I am facing is that I am neither able to select from the select box nor write in the input field as shown below. I have a feeling that I am using React's key inappropriately for mocFields in the render method which is obtained from getMOCField.
import React, { Component } from "react";
import { Button, Icon, Select, Form, Input } from "antd";
const FormItem = Form.Item;
const Option = Select.Option;
import { FormComponentProps } from "antd/lib/form/Form";
type state = {
mocFields: JSX.Element[]
};
export class MOC extends Component<FormComponentProps, state> {
constructor(props) {
super(props);
this.state = {
mocFields: []
};
this.addMOCField = this.addMOCField.bind(this);
this.removeMOCField = this.removeMOCField.bind(this);
}
componentDidMount() {}
componentWillReceiveProps(nextProps) {}
removeMOCField(key, event: React.MouseEvent<HTMLElement>) {
event.preventDefault();
const { mocFields } = this.state;
mocFields.splice(key, 1);
this.setState({
mocFields
});
}
getMOCFieldFooter() {
return (
<div className="d-flex justify-content-between small">
<div className="inline-block">
<Button
type="primary"
shape="circle"
icon="plus"
ghost
size="small"
className="d-font mr-1"
onClick={this.addMOCField}
/>
<div
className="text-primary pointer d-font inline-block letter-spacing-1"
onClick={this.addMOCField}
>
Add another
</div>
<div className="d-font inline-block letter-spacing-1">or </div>
<div className="text-primary pointer d-font inline-block letter-spacing-1">
Create a new MOC
</div>
</div>
</div>
);
}
getMOCField(key) {
const { getFieldDecorator } = this.props.form;
return (
<div className="d-flex justify-content-between">
<div className="inline-block">
<FormItem label="Select MOC">
{getFieldDecorator(`selected_moc[${key}]`, {
rules: [
{
required: true,
message: "Please select moc"
}
]
})(
<Select>
<Option value={"A"}>A</Option>
<Option value={"B"}>B</Option>
</Select>
)}
</FormItem>
</div>
<div className="inline-block">
<FormItem label="Recovery (%)">
{getFieldDecorator(`recovery_percentage[${key}]`, {
rules: [
{
required: true,
message: "Please input the recovery percentage"
}
]
})(<Input type="number" step="0.000001" />)}
</FormItem>
</div>
<div className="inline-block pointer">
<span>
<Icon type="close" onClick={this.removeMOCField.bind(this, key)} />
</span>
</div>
</div>
);
}
addMOCField(event: React.MouseEvent<HTMLElement>) {
event.preventDefault();
const { mocFields } = this.state;
const MOCField = this.getMOCField(mocFields.length);
mocFields.push(MOCField);
this.setState({
mocFields
});
}
getAddMOCButton() {
return (
<div className="d-flex w-100 mt-3">
<Button
type="primary"
ghost
className="w-100"
onClick={this.addMOCField}
>
<Icon type="plus-circle" />
Add MOC
</Button>
</div>
);
}
render() {
const { mocFields } = this.state;
const mocButton = this.getAddMOCButton();
const toRender =
mocFields.length > 0 ? (
<div className="w-100 p-2 gray-background br-25">
{mocFields.map((f, index) => (
<div key={index}>{f}</div>
))}
{this.getMOCFieldFooter()}
</div>
) : (
mocButton
);
return toRender;
}
}
What could be the reason for this? What am I doing incorrectly? Currently the above component renders as follows:
If the number of fields in mocFields is zero, then a button to add new fields is rendered.
After the button is pressed, mocField is populated with the select box and input field as shown above. The key of the div is decided during the render method.
It seems that the listeners doesn't work once they are stored in the array. I've tried to inline the call to getMOCField in the render function and it works. Here is what I've changed to get it work:
class MOC extends Component {
// ...
addMOCField(event) {
event.preventDefault();
const { mocFields } = this.state;
// We only keep inside the state an array of number
// each one of them represent a section of fields.
const lastFieldId = mocFields[mocFields.length - 1] || 0;
const nextFieldId = lastFieldId + 1;
this.setState({
mocFields: mocFields.concat(nextFieldId),
});
}
removeMOCField(key, event) {
event.preventDefault();
this.setState(prevState => ({
mocFields: prevState.mocFields.filter(field => field !== key)
}));
}
render() {
const { mocFields } = this.state;
const mocButton = this.getAddMOCButton();
const toRender =
mocFields.length > 0 ? (
<div className="w-100 p-2 gray-background br-25">
{/* {mocFields.map((f, index) => (
<div key={index}>{f}</div>
))} */}
{mocFields.map(fieldIndex => (
<div key={fieldIndex}>{this.getMOCField(fieldIndex)}</div>
))}
{this.getMOCFieldFooter()}
</div>
) : (
mocButton
);
return toRender;
}
}

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.

Categories

Resources