I have some data in arrays. I am getting it by using map, as you see in the below example. Also, i pass that into the button. Now, if, i select a button, it will get selected. But, if i select the next button, the previous button will get unselected and the current button will get selected. I don't want it to happen. I want to select multi buttons, if it all get clicked.
Thanks in advance.
Below is the Solution
import React, { Component } from 'react';
const BUTTONS = [
{id:0, title:'button1'},
{id:1, title:'button2'},
{id:2, title:'button3'}
]
class Map extends Component {
constructor(props){
super(props);
this.state = {
values: []
}
}
handleButton = button => {
let tmp = this.state.values;
if (this.state.values.includes(button)) {
this.setState({
values: this.state.values.filter(el => el !== button)
})
} else {
tmp.push(button);
this.setState({
values: tmp
})
}
}
render () {
return (
<div>
{BUTTONS.map(bt=>(
<button
key={bt.id}
onClick={()=>this.handleButton(bt.id)}
className={this.state.values.includes(bt.id) ? "buttonPressed" : "button"}>
{bt.title}
</button>
))}
</div>
);
}
}
export default Map;
Selecting multiple buttons
you'd better use the state as an array.
this.state = {
values: []
}
and you can push items.
let tmp = this.state.values;
tmp.push(button);
this.setState({
values: tmp
});
in render() you have to check state.values has bt.id
className={this.state.values.includes(bt.id) ? "buttonPressed" : "button"
Toggling multiple buttons
you can check in handleButton() whether that selected button is already selected
handleButton = button => {
if (this.state.values.includes(button)) {
this.setState({
values: this.state.values.filter(el => el !== button)
})
}
const BUTTONS = [
{ id: 0, title: 'button1' },
{ id: 1, title: 'button2' },
{ id: 2, title: 'button3' }
]
class Map extends React.Component {
constructor(props) {
super(props);
this.state = {
values: []
}
}
handleButton = button => {
let tmp = this.state.values;
if (this.state.values.includes(button)) {
this.setState({
values: this.state.values.filter(el => el !== button)
})
} else {
tmp.push(button);
this.setState({
values: tmp
})
}
}
render() {
return (
<div>
{BUTTONS.map(bt => (
<button
key={bt.id}
onClick={() => this.handleButton(bt.id)}
className={this.state.values.includes(bt.id) ? "buttonPressed" : "button"}>
{bt.title}
</button>
))}
</div>
);
}
}
export default Map;
The reason your button is getting deselected is because you're overwriting this.state.value every time you click a button.
If you want multiple selections, you'll need to hold all of the selected items in the state, as an array, and then when rendering, check if the button id is included in that array.
Something like:
import React, { Component } from 'react';
const BUTTONS = [
{id:0, title:'button1'},
{id:1, title:'button2'},
{id:2, title:'button3'}
]
class Map extends Component {
constructor(props){
super(props);
this.state = {
selectedValues: []
}
}
handleButton = buttonId => {
let newSelectedValues = this.state.selectedValues;
newSelectedValues.push(buttonId);
this.setState({
selectedValues: newSelectedValues
})
}
render () {
return (
<div>
{BUTTONS.map(bt => (
<button
key={bt.id}
onClick={()=>this.handleButton(bt.id)}
className={this.state.selectedValues.includes(bt.id) ? "buttonPressed" : "button"}>
{bt.title}
</button>
))}
</div>
);
}
}
export default Map;
if you need multiple selections you need an array:
this.state = {
values:[]
}
and push it on each clicks
Correct way to push into state array
Related
I have a checkbox component, I want my user to be able to check multiple items, and then the items to be saved in the state as an array.
If I select a checkbox my handleChange function seems to set my array to undefined, I'm not sure if it's the way I am sending the data or If I've setup my checkbox wrong, I'm quite new to React.
My main component is
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
this.setState({[input]: event.target.value})
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
My event form component
export default class EventFormat extends Component {
state = {
eventFormats: [
{id: 1, value: 1, label: "Virtual", isChecked: false},
{id: 2, value: 2, label: "Hybrid", isChecked: false},
{id: 3, value: 3, label: "Live", isChecked: false},
]
}
saveAndContinue = (e) => {
e.preventDefault()
}
render() {
return (
<Form>
<h1 className="ui centered">Form</h1>
<Form.Field>
{
this.state.eventFormats.map((format) => {
return (<CheckBox handleChange={this.props.handleChange} {...format} />)
})
}
</Form.Field>
<Button onClick={this.saveAndContinue}>Next</Button>
</Form>
)
}
}
And finally my checkbox component
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
export default CheckBox
The error is in your handleChange function, which sets state to a dictionary while you said you want the checkbox's value to be added to the eventFormats array in the state.
export default class MainForm extends Component {
state = {
eventFormats: []
}
handleChange = input => event => {
if (event.target.checked) {
this.setState({eventFormats: this.state.eventFormats.concat([event.target.value])});
} else {
const index = this.state.indexOf(event.target.value);
if (index === -1) {
console.error("checkbox was unchecked but had not been registered as checked before");
} else {
this.setState({eventFormats: this.state.eventFormats.splice(index, 1);
}
}
console.log(this.state)
}
render() {
const eventFormat = {eventFormats: this.state.eventFormats}
return <EventFormat
nextStep={this.nextStep}
handleChange={this.handleChange}
values={eventFormat}
}
}
}
There are a few things to fix:
this.setState({[input]: event.target.value})
this will always overwrite the array(eventFormats) with event.target.value.
<CheckBox handleChange={this.props.handleChange} {...format} />
in the above line, you're passing all the properties in each format object
const CheckBox = (props) => {
return (<Checkbox label={props.label} onChange={props.handleChange('eventFormats')}/>)
}
but here you're only using label and handleChange.
Here's a React StackBlitz that implements what you're looking for. I used <input type="checkbox" />, you can replace this with the Checkbox component you want. See the console logs to know how the state looks after toggling any of the checkboxes.
Also, added some comments to help you understand the changes.
const Checkbox = ({ id, checked, label, handleChange }) => {
return (
<>
<input
type="checkbox"
id={id}
value={checked}
// passing the id from here to figure out the checkbox to update
onChange={e => handleChange(e, id)}
/>
<label htmlFor={id}>{label}</label>
</>
);
};
export default class App extends React.Component {
state = {
checkboxes: [
{ id: 1, checked: false, label: "a" },
{ id: 2, checked: false, label: "b" },
{ id: 3, checked: false, label: "c" }
]
};
handleChange = inputsType => (event, inputId) => {
const checked = event.target.checked;
// Functional update is recommended as the new state depends on the old state
this.setState(prevState => {
return {
[inputsType]: prevState[inputsType].map(iT => {
// if the ids match update the 'checked' prop
return inputId === iT.id ? { ...iT, checked } : iT;
})
};
});
};
render() {
console.log(this.state.checkboxes);
return (
<div>
{this.state.checkboxes.map(cb => (
<Checkbox
key={cb.id}
handleChange={this.handleChange("checkboxes")}
{...cb}
/>
))}
</div>
);
}
}
I have two components, a TrackSection(the Parent element) which has a button that creates a TrackItem(child) every time it is clicked. The child elements are built through a variable numTracks which increments every time the button is clicked. The add button works fine but i'm having issues deleting a TrackItem from the array. I tried referencing the track_items directly but it won't let me.
I'm very new to React and Frontend development. Any other tips would be appreciated!
TrackSection.js
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
numTracks: 0,
};
}
onAddTrack = () => {
this.setState({
numTracks: this.state.numTracks + 1,
});
};
onDeleteTrack = () =>{
//????
};
render() {
const track_items = [];
for (var i = 0; i < this.state.numTracks; i += 1) {
track_items.push(<TrackItem key={i} id={i} onDeleteTrack = {this.onDeleteTrack(i)}/>);
}
return (
<div>
<Button onClick={this.onAddTrack}>
+new track
</Button>
{track_items}
</div>
);
}
}
TrackItem.js
class TrackItem extends Component{
constructor(props){
super(props);
this.state = {
id: this.props.id,
name: '',
}
}
render(){
var onDeleteTrack = this.props.onDeleteTrack
return(
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete</button>
</Grid>
);
}}
Issue
You are using an array index as the React key, and the id. When you remove an element from the array you may remove it from the array, but since the items shift up to fill the "hole" now all the elements in the array have incorrect "id"/index values.
Solution
Don't use a mapped array index as a React key.
Example solution uses an incrementing id, as before, but also stores the array in state. This allows you to consistently increment the id key and retain a static id with each element.
class TrackItem extends Component {
constructor(props) {
super(props);
this.state = {
id: this.props.id,
name: ""
};
}
render() {
var onDeleteTrack = this.props.onDeleteTrack;
return (
<Grid container direction="row">
<Grid item direction="column">
//Dummy
</Grid>
<button onClick={() => onDeleteTrack(this.props.id)}>Delete {this.props.id}</button>
</Grid>
);
}
}
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: [],
id: 0,
};
}
onAddTrack = () => {
this.setState(prevState => ({
tracks: [...prevState.tracks, prevState.id],
id: prevState.id + 1,
}));
};
onDeleteTrack = (id) =>{
this.setState(prevState => ({
tracks: prevState.tracks.filter(el => el !== id)
}))
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>
+new track
</button>
{this.state.tracks.map(track => (
<TrackItem key={track} id={track} onDeleteTrack = {this.onDeleteTrack}/>
))}
</div>
);
}
}
Be careful about doing too much logic in your render function, as your current solution would recreate all the TrackItem's every time you add a new item. So React can't do optimization magic.
Second remark, now you are just having a counter, so removing a element in the middle would probably not have the effect you are looking for. I assume the track items will be having some data to them. Like name, etc. So just store those values in the state and render each item.
Here is a sample solution, modify for your needs:
class TrackSection extends Component {
constructor(props) {
super(props);
this.state = {
tracks: []
};
}
onAddTrack = () => {
// Probably not the best way to create a id
const randomId = Math.random().toString();
const newTrack = {
id: randomId,
name: "Some name" + randomId
};
const newTracks = [
// the tracks we allready have added
...this.state.tracks,
// add a new track to the end
newTrack
];
// Replace state
this.setState({
tracks: newTracks
});
};
onDeleteTrack = (id) => {
// Keeps all tracks that don't match 'id'
const tracksWithOutDeleted = this.state.tracks.filter(
(track) => track.id !== id
);
// Replace the tracks, so now its gone!
this.setState({
tracks: tracksWithOutDeleted
});
};
render() {
return (
<div>
<button onClick={this.onAddTrack}>+new track</button>
{
// Loop over tracks we have in state and render them
this.state.tracks.map((track) => {
return (
<TrackItem
id={track.id}
name={track.name}
onDeleteTrack={this.onDeleteTrack}
/>
);
})
}
</div>
);
}
}
And the TrackItem.js:
class TrackItem extends Component {
render() {
const { onDeleteTrack, id, name } = this.props;
return (
<>
<button onClick={() => onDeleteTrack(id)}>Delete {name}</button>
</>
);
}
}
I am trying to achive a shared counter between components for example
I have 3 buttons
<Button />
<Button />
<Button />
and each of them has a label that will show a number when its clicked on it
when I click one of them it will start by 1 and only the one i clicked will show that number 1 and others will be 0 or no label at all then if i click other button that button wil show 2 on it
and previous one will stay at 1 and so on when i click third button it will have state of 3
which is a shared state between 3 same component instance
I am trying to achive a gallery image selection by this i will show a label selected items and their order by numbers on it how to achive it with react hooks
Sharing logic between siblings can be handled via a parent component that passes callback functions to each child.
import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';
interface AppProps { }
interface AppState {
photos: PhotoData[];
}
type PhotoData = { id: number; name: string; }
type GalleryProps = { photos: PhotoData[] }
const Gallery : React.FunctionComponent<GalleryProps> = ({
photos
}) => {
// Create an array to store the unique ID of the selected "photos".
// As items are selected they will be inserted into the array in the order
// they were selected.
const [selected, setSelected] = React.useState<number[]>([]);
// Create a function to handle the selection of a button.
const onSelect = React.useCallback((selectedId?: number) => {
setSelected(currentSelected => {
// If item already in the array, remove it.
if (currentSelected.some(id => id === selectedId)) {
return currentSelected.filter(id => id !== selectedId);
}
// if item not in the array, add it.
return [...currentSelected, selectedId ]
});
}, [setSelected])
return (
<div>
{
!!photos && photos.map(photo => {
// find the selection order using index.
const index = selected.indexOf(photo.id);
// change index to 1 based (or undefined if not selected).
const selectionOrder = index >= 0 ? index + 1 : undefined
return (
<Photo
key={photo.id}
{...photo}
onClick={onSelect}
index={selectionOrder}
/>
);
})
}
</div>
);
}
type PhotoProps = PhotoData & { index?: number; onClick: (selectedId: number) => void; }
const Photo : React.FunctionComponent<PhotoProps> = ({
id,
name,
onClick,
index
}) => {
// Create function to handle button click.
const onButtonClick = React.useCallback(() => {
onClick(id)
}, [onClick, id])
return (
<button
onClick={onButtonClick}
>
{name} {!!index && `(Selected ${index})`}
</button>
);
};
class App extends Component<AppProps, AppState> {
constructor(props) {
super(props);
this.state = {
photos: [
{
id: 1,
name: 'Photo 1'
},
{
id: 2,
name: 'Photo 2'
},
{
id: 3,
name: 'Photo 3'
}
]
};
}
render() {
return (
<div>
<Gallery photos={this.state.photos} />
</div>
);
}
}
render(<App />, document.getElementById('root'));
Runnable Version of the above.
How can I change the value of an array from inside a map function using button onClick event. In the following example I defined and array name visitMyArray that has three objects and initially the value of visited key is set as false. Using map function I render the all the location inside a paragraph tag. There will be a button rendered for each paragraph. Is it possible to change the value of the visited from false to true if possible how can I do it.
import React from "react";
import {Button} from 'react-bootstrap';
class VisitComponent extends React.Component {
render(){
let visitmyArray = [
{
location:"Indiana",
visited:false
},
{
location:"Illinoi",
visited:false
},
{
location:"Ohio",
visited:false
}
]
return(
<div>
{visitmyArray.map((visitedArray, index) => {
<div key={index}>
<p>{visitedArray.location}</p>
<Button onClick={"Change the visited value from 'false' to 'true' for this object value"}>Continue</Button>
</div>
)})}
</div>
}
}
export default VisitComponent
You can set the visited property to true for each item on the map. Your onClick now would be
onClick={() => {visitedArray.visited = true}}
Using state, it might look something like this:
import React from "react";
import {Button} from 'react-bootstrap';
class VisitComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
visitmyArray: [
{
location:"Indiana",
visited:false
},
{
location:"Illinoi",
visited:false
},
{
location:"Ohio",
visited:false
}
]
};
this.toggleVisited = this.toggleVisited.bind(this);
}
toggleVisited(location) {
return ev => {
var locations = this.state.visitmyArray.slice(0);
var loc = locations.find(a => a.location === location);
loc.visited = !loc.visited;
this.setState({visitmyArray:locations})
}
}
render(){
let {visitmyArray} = this.state;
return(
<div>
{visitmyArray.map((visitedArray, index) => (
<div key={index}>
<p>{visitedArray.location}</p>
<button className={visitedArray.visited ? "visited" : ""} onClick={this.toggleVisited(visitedArray.location)}>Continue</button>
</div>
))}
</div>
)
}
}
export default VisitComponent
You can define onClick as:
onClick = {() => {
visitmyArray[index].visited = true
}
}
I don't know your use case, but you shouldn't be defining the 'visitmyArray' in the render function. Every time the component renders, it will be redefined, so you should define it elsewhere. For instance,
let visitmyArray = [
{
location:"Indiana",
visited:false
},
{
location:"Illinoi",
visited:false
},
{
location:"Ohio",
visited:false
}
]
class VisitComponent extends React.Component {
render() {...}
}
If you want to listen to changes made to the array, you should define it as part of the component's state, like this:
let visitmyArray = [
{
location:"Indiana",
visited:false
},
{
location:"Illinoi",
visited:false
},
{
location:"Ohio",
visited:false
}
]
class VisitComponent extends React.Component {
constructor() {
super()
this.state = {
array: [...]
}
}
render() {...}
}
You should change the onClick to use the state's array to create a new array, and then use setState to actually modify it.
after let visitmyArray and before return( add:
markLocationAsVisited = (locationIndex) => {
this.visitmyArray[locationIndex].visited = true
}
and the in Button:
<Button onClick={() => markLocationAsVisited(index)}>
I have some problem and am getting furios a little. I want to .map my Array of Objects. And make everyone of them clickable. After click I want the particular object to show only its Avatar:
const stations = [
{
name: 'first',
avatar: './img/1.jpg'
},
{
name: 'second',
avatar: './img/2.jpg'
},
{
name: 'third',
avatar: './img/3.jpg'
},
{
name: 'fourth',
avatar: './img/4.jpg'
},
{
name: 'fifth',
avatar: './img/5.jpg'
}
]
Right now. I can access the value I need from my Database Array. But! I have a problem with:
this.state = {
clicked: false
}
this.handleClick = this.handleClick.bind(this)
}
My objects do not have separate state. So when I want to create some action based on this.state (like hide and show) it always work on EVERY element.
I have code which works in some way. When I render the list and click on any button, action occurs for every of them:
class radiosList extends React.Component {
constructor() {
super();
this.state = {
clicked: false
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(station) {
console.log(this)
this.setState({ clicked: !this.state.clicked })
}
render () {
return (
<div>
{
this.props.stations.map((station, index) => (
<div className='radio_list radio_list--component' style={{cursor: 'pointer'}} onClick={() => this.handleClick(station.avatar)}>
<div className='radio_list radio_list--component radio_list--component-station_name'>{station.name}</div>
</div>
))
}
</div>
)
}
}
export default radiosList
Edit: first answer helped with accessing values I need.
This is the way you can achieve what you want by adding an additional clicked state attribute to data. There could be a better way but his is how I have done it for my purposes so far.
class radiosList extends React.Component {
constructor() {
super();
this.state = {
data: [],
}
this.handleClick = this.handleClick.bind(this)
}
handleClick(index) {
console.log(this)
var data = [...this.state.data];
data[index].clicked = !data[index].clicked;
this.setState({data});
}
render () {
var self = this;
this.props.station.forEach(function(station) {
self.state.data.push({name: station.name, avatar: station.avatar, clicked: false});
self.setState({data: self.state.data});
})
return (
<div>
{
this.state.data.map((station, index) => (
<div className='radio_list radio_list--component' style={{cursor: 'pointer'}} onClick={() => this.handleClick(index)}>
<div className='radio_list radio_list--component radio_list--component-station_name'>{station.name}</div>
</div>
))
}
</div>
)
}
}
export default radiosList