I have several select element generated by a map. Aside from usingĀ jQuery to access the dom elements is there a way to get all the selected values onChange
changed = () => {
// Keep track of all the selected options
}
[1,2,3].map(value => (
<select onChange={changed}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>)
Whenever I select an option I would like to keep track of the option selected in an array. If in the first select I chose 1 then the second one 2 I'd like to have an array [1,2] representing the options picked. If I then select the third option to be 3 then the new array should be [1,2,3].In this case I want three separate select and I want to keep track the options selected in each
Assume you use hooks in your code. This should be like
import React, { useState, useEffect } from "react";
const App = () => {
useEffect(() => {
console.log("selections: ", selections);
});
const [selections, setSelections] = useState({});
const changed = (value, e) => {
setSelections({ ...selections, [`${value}`]: e.target.value });
};
return (
<div>
{[1, 2, 3].map(value => (
<select key={value} onChange={e => changed(value, e)}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
))}
</div>
);
};
export default App;
UPDATE: I updated my solution, sorry for missunderstand your questions, in case of multi dropdown list, your state should construct this way:
{ "1": 1, "2": 3, "3": 1}
Key is dropdown identifier and value is the selected option for it.
I've written examples using both React class and functional component for you.
If you want to use select with multiple values, you will have to set select multiple attribute to true. Note that select is very difficult to style and you may consider using a custom Dropdown instead.
import React from 'react';
import ReactDOM from 'react-dom';
class ClassExample extends React.Component {
state = {
value: [],
}
handleOnChange = (e) => {
const { value: selectedValue } = e.target;
const { value } = this.state;
const newValue = [].concat(value);
const index = newValue.findIndex(v => v === selectedValue);
if (index > -1) {
newValue.splice(index, 1);
} else {
newValue.push(selectedValue);
}
this.setState({ value: newValue });
}
render() {
const { value } = this.state;
return (
<div>
<select
value={value}
multiple
onChange={this.handleOnChange}
>
{[1, 2, 3].map(v => <option key={v} value={v}>{v}</option>)}
</select>
<pre>
{JSON.stringify(value, null, 2)}
</pre>
</div>
)
}
}
const FunctionExample = () => {
const [value, setValue] = React.useState([]);
const handleOnChange = (e) => {
const { value: selectedValue } = e.target;
const newValue = [].concat(value);
const index = newValue.findIndex(v => v === selectedValue);
if (index > -1) {
newValue.splice(index, 1);
} else {
newValue.push(selectedValue);
}
setValue(newValue);
}
return (
<div>
<select
value={value}
multiple
onChange={handleOnChange}
>
{[1, 2, 3].map(v => <option key={v} value={v}>{v}</option>)}
</select>
<pre>
{JSON.stringify(value, null, 2)}
</pre>
</div>
)
}
const App = () => (
<>
<label>
Class : <ClassExample />
</label>
<label>
Function : <FunctionExample />
</label>
</>
)
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
Here is a working demo: https://codesandbox.io/s/react-controlled-multiple-select-g7shd?fontsize=14&hidenavigation=1&theme=dark
Related
I'm new to React, so please bear with me. For a onChange function, I need to check if a selected value in a UI is in an array.
export const getRestrictedChoices = () => {
return (<>{RestrictedChoices}</>)
};
const Choices = (
<>
// lots of choices
</>
)
const RestrictedChoices = (
<>
//subset of Choices
<Option value="Choice1">Choice1</Option>
<Option value="Choice2">Choice2</Option>
<Option value="Choice3">Choice3</Option>
<Option value="Choice4">Choice4</Option>
</>
)
I'd like to check if a value selected by user from Choices (dropdown) is in RestrictedChoices and then update state. I've tried something like this, which results in an error:
const onChoiceChange = (value) => {
if (value && getRestrictedChoices().includes(value)) {
// do something
}
}
I also tried in vain to refactor getRestrictedChoices, but with similar results:
export const getRestrictedChoices = () => {
return (<>{Choices.values()}</>)
};
Both Choices and RestrictedChoices are in a separate config file.
I'm I approaching the problem in the right way, and if so, what's the best way to test for membership in RestrictedChoices?
Hi I not 100% what your exact need but please see the below example if this helps you:
let choices = ["Ivan", "Dragmon", "Guilmon", "Voz Rasposa", "Omar"];
let restrictedChoices = ["Dragmon", "Guilmon"];
class App extends React.Component {
constructor(props) {
super(props);
this.state = { message: "" };
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const newName = e.target.value;
let msg = "";
if (restrictedChoices.find((x) => x === newName))
msg = "Your choice is " + newName;
else msg = "Your choice is " + newName + ". But it is rectricted.";
this.setState({ message: msg });
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
<select onChange={this.handleChange}>
{choices.map((n) => (
<option key={n} value={n}>
{n}
</option>
))}
</select>
</div>
);
}
}
You can run this on CodeSandbox
Give this a shot to see if it gets you on the right track.
let choices = ["Ivan", "Dragmon", "Guilmon", "Voz Rasposa", "Omar"];
let restrictedChoices = ["Dragmon", "Guilmon"];
const isInRestrictedChoices = (value) => restrictedChoices.indexOf(value) > -1;
console.log(isInRestrictedChoices('Dragmon')) // true
console.log(isInRestrictedChoices('Omar')) // False
I have a form with select element. The values for options for select element comes from an API. So, I have to dynamically create the options. But, I am unable to get the select element from DOM.
Following is the code that have tried. I tried to access select ID element with findDOMNode. None of this is getting the element.
What do I need to do to get the element selected?
componentDidMount() {
companyUserNames()
.then(result => {
const companyUsername = result;
console.log(result);
//output ==> [ { userName: "ABC",fullName: "ABC XYZ"}, {userName:
// "DEF",fullName: "DEF QRW"}]
companyUsername.forEach(role => {
console.log(role);
const roledynamic1 = document.getElementById("name1");
console.log(roledynamic3);
//output = null
const roledynamic2 = this.refs.name1
console.log(roledynamic3);
//output = undefiend
const roledynamic3 = ReactDOM.findDOMNode(this.refs.name1)
console.log(roledynamic3);
//output = null
const newchild1 = document.createElement("option");
newchild1.value = role.userName;
newchild1.text = role.fullName;
roledynamic3.add(newchild1);
});
})
.catch(error => {
console.log(error);
});
}
render(){
return(
<form>
//some input field
<div className='select'>
<select
name='userName'
id='name1'
ref="name1"
className='input common-input-style'
maxLength='255'
value={this.state.userName.value}
onChange={this.handleInputChange}
>
<option>Name</option>
</select>
</div>
//some input field
<form/>
)
}
findDOMNode accepts a component as an argument, not a ref.
Try using the ref directly it should hold the DOM node.
Note that findDOMNode is a deprecated API and should be avoided.
Also, as Amin Paks mentioned you should consider switching to the current style of using refs with createRef
Why do you need to access DOM node when you can easily work with state in react.
Below is the working code with codesandbox link:-
import React from "react";
import ReactDOM from "react-dom";
class App extends React.Component {
state = {
names: []
};
companyUserNames = () => {
return new Promise(resolve => {
return resolve([
{ userName: "ABC", fullName: "ABC XYZ" },
{ userName: "DEF", fullName: "DEF QRW" }
]);
});
};
componentDidMount() {
this.companyUserNames()
.then(result => {
this.setState({
names: result
});
})
.catch(error => {
console.log(error);
});
}
render() {
const { names } = this.state;
let namesList =
names.length > 0 &&
names.map((item, i) => {
return (
<option key={i} value={item.userName}>
{item.fullName}
</option>
);
});
return (
<form>
<div className="select">
<select className="input common-input-style" maxLength="255">
{namesList}
</select>
</div>
</form>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I have a dynamic form in react-js and some of my elements are checkbox/radio that one of them have a text input binded to it.
for example the question is:
What is your favorite color?
and the answers are:
- red
- blue
- green
- OTHER
and OTHER answer have a text input in front of it for user to typing his custom answer in it.
How can I bind that checkbox/radio to the relevant input text and get its value?
form
If you use a newer version of React, try the state hook.
Something along the lines of
import React, { useState } from 'react';
function Example() {
const [color, setColor] = useState('');
return (
<div>
<select value={color}
onChange={(e) => setColor(value)}>
{ ['red', 'blue', 'green', 'OTHER'].map((c) => <option key={c} value={c}>{c}</option>)}
</select>
{color === 'OTHER' && <input type="text"></input>}
</div>
);
}
https://reactjs.org/docs/hooks-state.html
Using Material UI, I have used a similar solution like this to add an "Other" checkbox that is fillable:
import React from "react";
import ReactDOM from "react-dom";
import FormGroup from "#material-ui/core/FormGroup";
import FormControlLabel from "#material-ui/core/FormControlLabel";
import Checkbox from "#material-ui/core/Checkbox";
import TextField from "#material-ui/core/TextField";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
options: ["red", "blue", "green", "other"],
filterOptions: ["red", "blue", "green"],
checkedValues: [],
otherValue: "other"
};
}
handleOther = () => event => {
let value = event.target.value;
this.setState({
otherValue: value
});
};
handleSaveOther = () => event => {
let newCheckedValues = [...this.state.checkedValues]; // make a separate copy of the array
let intersection = newCheckedValues.filter(x =>
this.state.filterOptions.includes(x)
);
let allValues = [...intersection, this.state.otherValue];
if (this.state.other) {
this.setState({
checkedValues: allValues
});
}
};
handleCheck = option => event => {
let value = event.target.value;
let checked = event.target.checked;
let newCheckedValues = [...this.state.checkedValues]; // make a separate copy of the array
let index = newCheckedValues.indexOf(value);
if (index !== -1) {
newCheckedValues.splice(index, 1);
this.setState({
checkedValues: newCheckedValues,
[option]: checked
});
} else {
this.setState({
checkedValues: [...this.state.checkedValues, value],
[option]: checked
});
}
};
render() {
const { options, checkedValues, otherValue } = this.state;
console.log(checkedValues);
return (
<div className="App">
<div style={{ width: "50%", margin: "0 auto" }}>
<FormGroup>
{options.map((option, i) => {
return (
<FormControlLabel
control={
<Checkbox
onChange={this.handleCheck(option)}
value={option === "other" ? otherValue : option}
color={"primary"}
/>
}
label={
option === "other" ? (
<TextField
id={"other"}
name={"other"}
value={this.state.otherValue}
fullWidth
onChange={this.handleOther()}
onBlur={this.handleSaveOther()}
/>
) : (
option
)
}
/>
);
})}
</FormGroup>
</div>
</div>
);
}
}
See working example here
I need to store the distance value in state. It should equal the distance passed as props + the distance selected by user. How to do this?
class Distance extends React.Component {
constructor(props) {
super(props);
this.state = {
distance: 0
};
}
onChange = e => {
}
render() {
return (
<div>
<p>{this.props.distance}</p>
<select onChange={this.onChange}>
<option>30km</option>
<option>50km</option>
<option>70km</option>
</select>
</div>
);
}
}
Using functional components, you can do this:
const Distance = () => {
const [distance, setDistance] = useState("");
return (
<div>
<p>{distance}</p>
<select onChange={(e) => setDistance({distance: e.target.value})}>
<option value="30">30km</option>
<option value="50">50km</option>
<option value="70">70km</option>
</select>
</div>
);
};
export default Distance;
In a case where you have multiple inputs, and only distance is a select input, you can do this to update distance while maintaining the values of other inputs:
const Distance = () => {
const [input, setInput] = useState({
distance: "",
time: "",
place: "",
});
return (
<div>
<p>{distance}</p>
<select onChange={(e) =>
setInput({ ...input, distance: e.target.value }}
>
<option value="30">30km</option>
<option value="50">50km</option>
<option value="70">70km</option>
</select>
</div>
);
};
export default Distance;
First add value attributes to your <option> elements, and then access the use the value of the <select> via e.currentTarget.value in your onChange handler like so:
class Distance extends React.Component {
constructor(props) {
super(props);
this.state = {
distance: 0
};
}
onChange = e => {
// Extract value of select like so. Use parseInt for
// improved type safety
const valueSelectedByUser = parseInt(e.target.value);
// Update distance in state via setState()
this.setState({ distance : this.props.distance + valueSelectedByUser });
}
render() {
return (
<div>
<p>{this.props.distance}</p>
<select onChange={this.onChange}>
<option value="30">30km</option>
<option value="50">50km</option>
<option value="70">70km</option>
</select>
</div>
);
}
}
You should include value for select and handle onChange event:
class Distance extends React.Component {
constructor(props) {
super(props);
this.state = {
distance: 0
};
}
onChange = e => {
this.setState({
distance: this.props.distance ? this.props.distance + e.target.value : e.target.value
});
}
render() {
return (
<div>
<p>{this.props.distance}</p>
<select onChange={this.onChange}>
<option value="30">30km</option>
<option value="50">50km</option>
<option value="70">70km</option>
</select>
</div>
);
}
}
you can simply do this like this, I have converted your code to functional components and also modified it, try this.
const Distance = () => {
const [distance, setDistance] = useState("");
return (
<div>
<p>{distance}</p>
<select onChange={(e) => setDistance(e.target.value)}>
<option value="30">30km</option>
<option value="50">50km</option>
<option value="70">70km</option>
</select>
</div>
);
};
export default Distance;
I have a component which renders Input type='select': (I am using reactstrap)
import React, {Component} from 'react'
import {
Form,
FormGroup,
Input,
Button,
Col,
} from 'reactstrap'
import {
withRouter,
} from 'react-router'
import Context from '../../../../../provider'
class NewPost extends Component {
constructor(props) {
super(props)
this.state = {
subreddits: [],
subreddit_selected: '',
subreddit_id: 0,
...
}
this.handleSubredditSelect = this.handleSubredditSelect.bind(this)
}
componentDidMount() {
fetch('/api/reddit/r/')
.then(data => data.json())
.then(json => {
this.setState({
subreddits: json,
...
})
})
}
handleSubredditSelect(event) {
console.log('selected id: ',event.target.id)
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
...
render() {
return (
<Context.Consumer>
{context => {
return (
<React.Fragment>
<Form
...
>
<FormGroup row>
<Col sm={7}>
<Input
type="select"
onChange={this.handleSubredditSelect}
required
>
<option key='0' disabled selected>Select an Option</option>
{this.state.subreddits.map((subreddit) => {
return (
<option key={subreddit.id} id={subreddit.id}>{'r/' + subreddit.name}</option>
)
})}
</Input>
</Col>
</FormGroup>
...
</React.Fragment>
)
}}
</Context.Consumer>
)
}
}
export default withRouter(NewPost)
So, I have a function handleSubredditSelect which does the following:
handleSubredditSelect(event) {
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
In this function I am not getting any value for event.target.id.
I have tried event.target.key as well but that returned an empty string "".
I want to set subreddit_id in state to the selected option's ID
The selecting does not work because event.target in <select> element returns entire tree with options:
// result of event.target:
<select>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
Instead the selected one.
For accessing the current option element from select you should rely on selectedIndex:
event.target[event.target.selectedIndex].id
The code:
export default class SelectForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "some1",
id: "id-1"
};
}
handleChange = event => {
console.log("event", event.target, event.target.selectedIndex);
this.setState({
value: event.target.value,
id: event.target[event.target.selectedIndex].id
});
};
render() {
return (
<div>
<select value={this.state.sex} onChange={this.handleChange}>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
ID: {this.state.id}
</div>
);
}
}
You can use selectedIndex attribute of select:
handleSubredditSelect(event) {
const selectedOption = event.target.childNodes[event.target.selectedIndex];
this.setState({
subreddit_selected: event.target.value,
subreddit_id: selectedOption.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
Here is the sandbox: https://codesandbox.io/s/n5679m5owj
AFAIK event.target.id should be working, doing the same in my project.
But should't it be
<Input
type="select"
onChange={(e) => this.handleSubredditSelect}
required>`
? (No parantheses after the methodname)