I have an issue with react when I want to change the selected option.
The problem is that the value is an object and I can't pass it in option value attribut.
See the following code:
class Selector extends React.Component {
contructor(props) {
super(props)
this.state = { obj: null }
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.setState({obj: e.target.value})
}
render() {
<select onChange={handleChange}>
{this.props.listOption.map((option, index) =>
<option key={index} value={option.obj}>
{option.name}
</option>
)}
</select>
}
}
and with
<Selector option={[{name: "name", obj:{...}}, ...]}>
I need to change the state of the component with the value of the selected option.
What I get when the state change is "object Object". I suppose this happens because react can't embed javascript object in attribut of final view. I am right?
Moreover, I set obj in state as null in the constructor
Is there a right way to do it?
You can make use of index of options
class Selector extends React.Component {
contructor(props) {
super(props);
this.state = { obj: null }
this.handleChange = this.handleChange.bind(this)
}
handleChange(e) {
this.setState({obj: this.props.listOption[e.target.value].obj})
}
render() {
<select onChange={handleChange}>
{this.props.listOption.map((option, index) =>
<option key={index} value={index}>
{option.name}
</option>
)}
</select>
}
}
Moreover, I set obj in state as null in the constructor Is there a
right way to do it?
I depends on your requirement. If you want to show at least one option as selected you can keep that instead of null
Convert the object to JSON string, and pass it as value.
And convert the string back to object in the handler.
handleChange(event) {
let obj = JSON.parse(event.target.value); //object
}
render() {
<select onChange={handleChange}>
{this.props.listOption.map((option, index) =>
<option key={index}
value={JSON.stringify(option)}> //pass object string as value
{option.name}
</option>
)}
</select>
}
I assume you want only one option will be selected.
So the easiest way would be to set selectedIndex.
When using construct always think of value type.
this.state = { selectedIndex: 0}
Now you've state with selectedIndex object which firstly is equal to 0.
In render method you could then just check for the index:
{this.props.listOption.map((option, index) => {
this.state.selectedIndex == index ? (
<option key={index} value={option.obj} selected>option.name</option>
): (
<option key={index} value={option.obj}>option.name</option>
))}
And on handle change setState with e.target.key.
I may have left syntax errors... Altought I hope it helps.
Try this following code,
import React from 'react';
class LocationDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
searchLoc: undefined,
selectedLoc: "",
locs:[
{"name" : "Kerala","districts":["Ernakulam", "Trivandrum"]},
{"name" :"Tamil Nadu","districts" :["Palani","Tiruchi"]}
],
};
this.handleChangeLocation = this.handleChangeLocation.bind(this);
}
handleChangeLocation = (event) => {
this.setState({ selectedLoc: event, searchLoc: event.target.value }
, () => console.log("searchLoc", this.state.searchLoc));
}
render() {
return (
<select class="cls-location" id="id-location"
onChange={this.handleChangeLocation}
value={this.state.locs.find(obj => obj.value === this.state.selectedLoc)}
required
>
{
this.state.locs.map(loc => {
return (
<option name={loc.name} value={JSON.stringify(loc)}>{loc.name}</option>
)
})
}
</select>
);
}
}
export default LocationDemo;
In my case, I also needed an option object (currentValue) to be selected at init.
I do not want to search for that object in this.props.listOption to get its index.
So, instead of replacing the objects with their index, I added a custom attribute data-index to the options.
The value of attribute data-index of an option option can be accessed using option.dataset.index:
handleChange(e) {
const selectedIndex = e.target.selectedOptions[0].dataset.index];
this.setState({obj: this.props.listOption[selectedIndex].obj})
}
render() {
<select value={currentValue} onChange={handleChange}>
{this.props.listOption.map((option, index) =>
<option key={index} value={option.obj} data-index={index}>
{option.name}
</option>
)}
</select>
}
That code should not be difficult to adapt to multiple selects.
Related
Currently, my dropdown looks like this. I can only select one of the options. I want to be able to select multiple options when clicking. I tried adding multiple in <select> but that doesn't work. How can I make the dropdown allow multiple selections?
const SelectMultipleDropdown = props => {
const {
name,
required,
placeholder,
handleChange,
choices,
value,
fieldValid,
setFieldValid
} = props;
const [currentSelection, setCurrentSelection] = useState("");
// the default value is empty string ""
// invalid/greyed out value is empty string ""
return (
<div>
<p className="field-component-title">
{name}
{required ? <span className="required-star"> *</span> : ""}
</p>
<select
className={`dropdown-select-field field-component-input-box ${
currentSelection === ""
? "dropdown-select-grey"
: "dropdown-select-black"
} ${(() => {
return fieldValid ? "" : "dropdown-select-invalid";
})()}`}
type="text"
onChange={e => {
e.persist();
setCurrentSelection(e.target.value);
handleChange(e);
setFieldValid(true);
}}
value={value}
>
<option value={""}>{placeholder}</option>
{choices.map(({ value, text }, index) => (
<option key={index} value={value}>
{text}
</option>
))}
</select>
</div>
);
};
I'm a bit unclear on the error or undesired behavior you get here. But, here's my try. First multiple works a bit weird depending on browser and OS as described here.
I'm guessing that's not what you're describing tho. I'm guessing the problem is that you are 1. overwriting all your selected and 2. not mapping selected to your options.
So you need to start with an empty array for your selected elements if nothing is selected, then add to that array in the onChange() instead of overwrite the value there and finally add selected to the option when it's in your list of selected elements.
I would also add it appears you are storing the value at this level component and a higher level component thru a callback. It is generally a best practice to store the value in one spot. I'm not sure the best place from this bit of code. That might be best in another question.
const [currentSelection, setCurrentSelection] = useState([]);
return (
<div>
<p className="field-component-title">
{name}
{required ? <span className="required-star"> *</span> : ""}
</p>
<select
className={`dropdown-select-field field-component-input-box ${
currentSelection === ""
? "dropdown-select-grey"
: "dropdown-select-black"
} ${(() => {
return fieldValid ? "" : "dropdown-select-invalid";
})()}`}
type="text"
onChange={e => {
e.persist();
setCurrentSelection((current) => [...current, e.target.value]);
handleChange(e);
setFieldValid(true);
}}
>
<option value={""}>{placeholder}</option>
{choices.map(({ value, text }, index) => (
<option key={index} value={value} selected={currentSelection.includes(value)}>
{text}
</option>
))}
</select>
</div>
);
};
Note: if you want to support super old browsers you'd need to replace includes(currentValue) with indexOf(currentValue) > -1
I think in this component, your current Selection is a string, and use setCurrentSelection(e.target.value); can change currentSelection to another option.
You can change string to array, for instance, currentSelections. And change setCurrentSelections function to:
setCurrentSelections = (e) => {
const value = e.target.value;
let currentSelections = this.state.currentSelections.slice(0);
let index = currentSelections.indexOf(value);
if (index > -1) {
currentSelections.splice(index, 1);
} else {
currentSelections.push(value);
}
this.setState({ currentSelections: currentSelections });
}
And use React Component
class SelectMultipleDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
currentSelections: [],
}
}
setCurrentSelections = (e) => {
const value = e.target.value;
let currentSelections = this.state.currentSelections.slice(0);
let index = currentSelections.indexOf(value);
if (index > -1) {
currentSelections.splice(index, 1);
} else {
currentSelections.push(value);
}
this.setState({ currentSelections: currentSelections });
}
onChange = (e) => {
e.persist();
this.setCurrentSelection(e);
this.porps.handleChange(e);
this.props.setFieldValid(true);
}
render() {
const {
name,
required,
placeholder,
choices,
value,
} = props;
return (
<div>
<p className="field-component-title">
{name}
{required ? <span className="required-star"> *</span> : ""}
</p>
<select
className={''}
type="text"
onChange={this.onChange}
value={value}
>
<option value={""}>{placeholder}</option>
{choices.map(({ value, text }, index) => (
<option key={index} value={value} selected={this.state.currentSelections.includes(value)}>
{text}
</option>
))}
</select>
</div>
);
}
}
Need some help with dropdowns. I can't seem to figure out a way to pass in the ID specific to that particular dropdown <option> so that when the user makes a selection that value can be used (for example to setState). The usual case is to read the value of the dropdown itself, but in my case I don't want the displayed value but another value that is 'behind the scenes' or hidden.
Please note I've already researched about controlled components in react for forms etc, this issue is a bit different and my attempts to find an answer have come up empty.
I'm using react and javascript but I think if you know a little about javascript you may be able to help.
The dropdowns are set up like shown below where on changing the dropdown selection it runs the function handleStatusChange.
<select
id="inputIdentity"
className="select-form"
onChange={this.handleStatusChange}
>
<option value="" hidden>
please choose
</option>
<option>status1</option>
<option>status2</option>
<option>status3</option>
</select>
In actual practice the options are mapped out from data fetched via an api as they can be altered in the backend:
<select
id="inputIdentity"
className="form-control content-input"
onChange={this.handleStatusChange}
>
<option value="" hidden>
Please Choose
</option>
{statusData ? (
statusData.map((status) => (
<option>{status.title}</option>
))
) : (
<option>Loading...</option>
)}
</select>
this.handleStatusChange checks the value of the event (which is the act of changing the selection in the dropdown, the value is accessed with: e.target.value) to read the value inside the <option> that was chosen... The method for reading the chosen dropdown value is something like:
handleStatusChange = (e) => {
this.setState({
// set it to state
status: e.target.value
});
};
I
This is the standard way to do it.
Now we get back to the question - is there a way to read a value from each dropdown that is NOT shown (read from e.target.value) instead, ie. if they each had an ID or something, how do I pass that in so that e.target.value would be able to access this id.
If you take a look at the version where I map out (or loop out if you aren't familiar with the map function) the <option> tags and in each iteration I pass in the title with {status.title}. I can access the id with status.id but the onChange handler is in the <select> tag and can't be within the map / loop, so to handle it by passing in the id into the onChange handler is not possible either.
What would be the correct way for the onChange handler to have access to that specific value which is not displayed between <option> tags?
You can keep the options in your component state and use the array method find to find the option that corresponds to the selected option and use that.
Example
class App extends React.Component {
state = {
options: [
{ value: "status1", label: "Status 1", secretValue: "foo" },
{ value: "status2", label: "Status 2", secretValue: "bar" },
{ value: "status3", label: "Status 3", secretValue: "baz" }
],
selectedOption: ""
};
handleStatusChange = e => {
const { value } = e.target;
this.setState(prevState => {
const { secretValue } = prevState.options.find(
option => option.value === value
);
console.log(secretValue);
return { selectedOption: value };
});
};
render() {
const { options, selectedOption } = this.state;
return (
<select value={selectedOption} onChange={this.handleStatusChange}>
<option value="" hidden>
please choose
</option>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
);
}
}
ReactDOM.render(<App />, 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>
you can pass that value to the loop showing the Options.
{statusData ? (
statusData.map((status) => (
<option value={status.value}>{status.title}</option>
))
) : (
<option>Loading...</option>
)}
and in order to do that you have to modify your array statusData.
hiddenData = ['a', 'b', 'c'];
statusData.map((s, index) => {
s.value = hiddenData[index]
return s;
});
is there a way to read a value from each dropdown that is NOT shown (read from e.target.value) instead, ie. if they each had an ID or something, how do I pass that in so that e.target.value would be able to access this id.
e.target is a reference to the HTMLSelectElement where the change occurred. You can find the option with the matching value in its options list, and then use that HTMLOptionElement's properties, like this:
handleStatusChange({target}) {
const value = target.value;
const optionElement = Array.from(target.options).find(opt => opt.value === value);
// If found, use information from `optionElement` to find the
// entry in `statusData` in a state change, e.g.:
if (optionElement) {
const id = optionElement && optionElement.id;
if (id) {
this.setState(state => {
const entry = state.statusData.find(e => e.id === id);
if (entry) {
// Use `entry`'s information
}
}
}
}
}
React example, using a details property on the entries in statusData:
class Example extends React.Component {
constructor(...args) {
super(...args);
this.handleStatusChange = this.handleStatusChange.bind(this);
this.state = {
detail: "",
statusData: [
{id: "one", title: "One", detail: "Details for one"},
{id: "two", title: "Two", detail: "Details for two"},
{id: "three", title: "Three", detail: "Details for three"}
]
};
}
handleStatusChange({target}) {
const value = target.value;
const optionElement = Array.from(target.options).find(opt => opt.value === value);
const id = optionElement && optionElement.id;
if (id) {
this.setState(state => {
const entry = state.statusData.find(e => e.id === id);
if (entry) {
return {
detail: entry.detail
}
}
});
}
}
render() {
const {statusData, detail} = this.state;
return (
<div>
<select
id="inputIdentity"
className="form-control content-input"
onChange={this.handleStatusChange}
>
<option value="" hidden>
Please Choose
</option>
{statusData ? (
statusData.map((status) => (
<option id={status.id}>{status.title}</option>
))
) : (
<option>Loading...</option>
)}
</select>
<div>Detail: {detail}</div>
</div>
);
}
}
ReactDOM.render(
<Example />,
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>
How can I link to a value when selected onChange in a select box?
Looking to implement a select menu into ReactJS that links to the value onChange.
render() {
return (
<select onChange={() => {if (this.value) window.location.href=this.value}}>
<option value="">Please select</option>
{pages.map(({ node: page })=> (
<option key={page.id} value="{page.slug}">{page.title}</option>
))}
</select>
);
}
This is getting the value (I believe) but I keep getting the error of Cannot read property 'value' of undefined
I have tried following the documents here as suggested in some answers yet I have not been able to get this working with my current code - see as follows the full Page.js
import React from 'react'
import Helmet from 'react-helmet'
import styled from 'styled-components'
import config from '../utils/siteConfig'
const PageCompany = ({data}) => {
const {title,slug} = data.contentfulCompanyPage;
const pages = data.allContentfulCompanyPage.edges;
return(
<Wrapper>
<CompanyMenu>
<div>
<select onChange={() => {if (this.value) window.location.href=this.value}}>
<option value="">Please select</option>
{pages.map(({ node: page })=> (
<option key={page.id} value="{page.slug}">{page.title}</option>
))}
</select>
</div>
</CompanyMenu>
</Wrapper>
)
}
export const companyQuery = graphql`
query companyQuery($slug: String!) {
contentfulCompanyPage(slug: {eq: $slug}) {
title
slug
keywords
description
heroBg {
sizes(maxWidth: 1500) {
src
}
}
}
allContentfulCompanyPage(sort: {fields: [menuOrder], order: ASC}) {
edges {
node {
id
title
slug
}
}
}
}
`
export default PageCompany
Instead of making use of Global window.location property you can make a separate method handleChange like :
constructor(props) {
super(props);
this.state = { }; // initialise state
// Make sure to bind handleChange or you can make use of arrow function
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
const targetValue = e.target.value;
// Then you can do whatever you want to do with the value
this.setState({
[name]: targetValue
});
EDIT : In order to make use of constructor make sure you are defining components using class syntax like:
import React , { Component } from 'react';
class PageCompany extends Component {
constructor(props) {
super(props);
this.state = { }; // initialise state
this.handleChange = this.handleChange.bind(this);
}
// Make sure class has a render method
render () {
return ()
}
}
And inside your <Select> You can reference it to handleChange
<select onChange={this.handleChange}>
You can read more about onChange Here
You need to pass the event param and then grab the value from the target of that event e.g.
onChange={(event) => this.setState({value: event.target.value})}
There's a great example here.
Full code excerpt from linked docs:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
I am trying to get a select to show/hide on check but the select just renders and does not disappear nor reappear. I am fairly new to react, so I am sure I am doing something wrong.
export default class TreeTest extends Component {
constructor() {
super();
this.state = {
checked: [
'/grantSettingsPermissions/Admin',
'/grantSettingsPermissions/ContentGroups/AddLocations',
],
expanded: [
'/grantSettingsPermissions',
'/grantSettingsPermissions/ContentGroups',
],
};
this.onCheck = this.onCheck.bind(this);
this.onExpand = this.onExpand.bind(this);
this.handleChange = this.handleChange.bind(this);
}
onCheck(checked) {
console.log(checked);
this.setState({
checked,
});
}
onExpand(expanded) {
this.setState({
expanded,
});
}
handleChange() {
this.setState({
checked: !this.state.checked,
});
}
render() {
const { checked, expanded } = this.state;
const content = this.state.checked
? <select>
<option value="test1">test1</option>
<option value="test2">test2</option>
</select>
: null;
return (
<div>
{ content }
<CheckboxTree
checked={checked}
expanded={expanded}
nodes={nodes}
onCheck={this.onCheck}
onExpand={this.onExpand}
expandDisabled={true}
onChange={ this.handleChange }
/>
</div>
);
}
}
I have a feeling I just need to add stuff to the onCheck function, but I am not entirely sure. Any help would be awesome!
Your condition should be:
const content = this.state.checked.length === 0
? <select>
<option value="test1">test1</option>
<option value="test2">test2</option>
</select>
: null;
I'm not sure what your component CheckboxTree does, but here is some info that applies to regular input controls:
Your event handler onChecked is expecting checked to be the value of your checkbox, but in fact it will be an event object. So you need to get the value from the event object and set the state with that:
onCheck(e) {
console.log(e);
let checked = {checked: e.target.value}
this.setState({
checked,
});
}
UPDATE
I see from the documentation that they are doing it the same way, so it should work, because your code is equivalent to this:
onCheck={checked => this.setState({ checked })}
onExpand={expanded => this.setState({ expanded })}
Whats the approved way to create select element in react, which is two way bound with the prop of selection containing component? The default selection should be the present attribute of the prop (may be generated, because the value is arbitrary, and on selection the prop attribute should reflect the selection. Also, it should be possible to write the value directly to the selection field.
There isn't an "approved" way as such, but you should note a couple of things:
The change event is triggered on the element, not the element.
Controlled and uncontrolled components defaultValue are set differently.
This is a generic example of a controlled dropdown menu
var MyDropdown = React.createClass({
getInitialState: function() {
return {
value: 'select'
}
},
change: function(event){
this.setState({value: event.target.value});
},
render: function(){
return(
<div>
<select id="fruit" onChange={this.change} value={this.state.value}>
<option value="select">Select</option>
<option value="Apples">Apples</option>
<option value="Mangoes">Mangoes</option>
</select>
<p></p>
<p>{this.state.value}</p>
</div>
);
}
});
React.render(<MyDropdown />, document.body);
and here's a working demo.
I add the options to an array on state and then map overthem,
try this code
import React, { Component } from 'react'
class SelectExample extends Component {
constructor() {
super()
this.state = {
options: ['One', 'Tow', 'Three'],
selectedOption: 'One',
}
}
handleChange = e => {
this.setState({
[e.target.name]: e.target.value,
})
}
render() {
return (
<select name='selectedOption' onChange={this.handleChange}>
{this.state.options.map(i => i == this.state.selectedOption ? (
<option value={i} selected>
{i}
</option>
) : (<option value={i}>{i}</option>) )}
</select>
)
}
}