this might be a dumb question, but I'm new to react and I'm having some difficulty with my drop-down menu. Long story short, I've got a list of locations, and I need to be able to 'pull out' whichever location the user picks from the menu (I plan on using that location info to generate a map using Google's static maps API).
<form onSubmit={this.handleSubmit}>
<select className="menu" name="select" value={this.state.value} onChange={this.searchLocations}>
{options}
</select>
<input type="submit" value="Submit" />
</form>
(and this is the code that generates {options}):
let options = this.state.locationsBase.map((item, index) => {
return(
<option key={index + 1} value={this.state.locationsBase}>{item.name}</option>
)
})
locationsBase is an array that's loaded in with componentDidMount.
So my problem is this:
<select value= > is returning 'undefined', when I need it to return whichever location the user clicks on (and the values for locations are contained in {options} ). (also, these locations all show up in the drop-down menu).
I don't know if this is very clear, but any help would be greatly appreciated. Thanks.
update
this is what the searchLocations function looks like:
searchLocations(e){
e.preventDefault()
let selectedLocation = this.locationQuery
console.log(selectedLocation)
this.setState({
locationResults: this.state.selectedLocation
})
selectedLocation returns undefined in the console.
and locationQuery is set to this.value in my initial state.
update (full component):
class LocationsContainer extends Component {
constructor(props){
super(props)
this.state = {
locationQuery: this.value,
locationResults: "",
locationsBase: []
}
this.handleOnChange = this.handleOnChange.bind(this)
this.searchLocations = this.searchLocations.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
componentDidMount(){
this.setState({
locationsBase:[
{name: "Pick a branch" , address: ""},
{name: "Anacostia" , address: "1800 Good Hope Rd SE"},
{name: "Bellevue" , address: "115 Atlantic St SW"},
{name: "Benning" , address: "3935 Benning Rd NE"},
{name: "Capitol View" , address: "5001 Central Ave SE"},
]
}, () => {
console.log(this.state.locationsBase)
})
}
handleOnChange(e) {
const name = e.target.value
this.setState({
[name]: e.target.value
})
console.log("name")
}
searchLocations(e){
e.preventDefault()
let selectedLocation = this.locationQuery
console.log(selectedLocation)
this.setState({
locationResults: this.state.selectedLocation
})
console.log(this.locationResults, 'something is working')
}
handleSubmit(e) {
alert('Your location is: ' + this.state.value);
event.preventDefault();
}
render (){
let options = this.state.locationsBase.map((item, index) => {
return(
<option key={index + 1} value={this.state.locationsBase}>
{item.name}</option>
)
})
return (
<div>
<h3>Choose your branch!</h3>
<form onSubmit={this.handleSubmit}>
<select className="menu" name="select" value={this.state.value}
onChange={this.searchLocations}>
{options}
</select>
<input type="submit" value="Submit" />
</form>
<p>{this.state.locationResults}</p>
</div>
)
}
}
export default LocationsContainer
It looks like you need to pass the onChange event to the searchLocations function.
<select onChange={( e ) => this.searchLocations( e ) }>
{options}
</select>
Then grab the value at e.target.value and pass that to your this.locationQuery function.
Hope this helps!
Related
I have a CustomInput from reactstrap that is not showing the correct state.name when clicked. I am passing a stringifiedJSON object as value on the option, then The handle change is parsing the string back into an object. I can console.log(state) and see the object just fine.
If I set the onChange to onChange={({target}) => setCountry(target.value)} this renders the selected option correctly, If I select united states, it shows united states, If I select Mexico it shows Mexico so on and so forth.
However, When I set my onChange to onChange={handleChange} with handleChange being this below, It will only show the first item in the array, In my case United States. I can select Mexico, the console.log will show the updated state, However the select option looks like United States is still chosen.
const handleChange = async(event) => {
setCountry(event.target.value)
const obj = JSON.parse(event.target.value)
setCountry(obj)
}
<Input
type="select"
id="country"
name="country"
className="mb-3"
value={country}
onChange={({target}) => setCountry(target.value)} <--- this renders correctly
>
{numberCountries.map((country, index) => (
<Fragment>
<option key={index} value={JSON.stringify(country)}>{country?.name}</option>
</Fragment>
))}
<Input
type="select"
id="country"
name="country"
className="mb-3"
value={country}
onChange={handleChange} <--- this calls update state and updates correctly but the select option wont render the correct country.name.
>
{numberCountries.map((country, index) => (
<Fragment>
<option key={index} value={JSON.stringify(country)}>{country?.name}</option>
</Fragment>
))}
</Input>
Here is the numberCountries thats being mapped over for the options
[
{
name: 'United States',
countryCode: 'US',
areaCodes: AreaCodes,
type: {
local: {
amount: '400'
},
toll_free: {
amount: '400'
},
}
},
{
name: 'Australia',
countryCode: 'AU',
type: {
local: {
amount: '1500'
},
}
},
{
name: 'Belgium',
countryCode: 'BE',
type: {
local: {
price: '410'
},
}
}
]
Instead of converting the whole country object to a string, you should use the countryCode property as its value.
Its not advised to use the index of an array as the key, so if you can use the countryCode as its key aswell.
<Input
type="select"
id="country"
name="country"
className="mb-3"
value={country.countryCode}
onChange={handleChange}
>
{numberCountries.map(({ countryCode, name }) => (
<Fragment>
<option key={countryCode} value={countryCode}>{name}</option>
</Fragment>
))}
</Input>
And then your handleChange function should be
const handleChange = async (event) => {
const obj = numberCountries.find(country => country.countryCode === event.target.value);
setCountry(obj);
};
Just remove setCountry(obj) in the handleChange
Updating state works if the state i am trying to update is outside the users array. But since i am having multiple users i need the state to be inside the objects and update anyway
I keep getting the error TypeError: Cannot read property 'name' of undefined
I've thought of setting state inside of a loop but i was told thats a bad idea.
So [e.target.name]: e.target.value was the only code i could find for dropdowns.
I tried passing id for each of the users but didnt know how to change state using that or what condition to put.
import React, { Component } from 'react'
export default class App extends Component {
state = {
users: [
{
id: uuid(),
firstName: 'John',
lastName: 'Doe',
favColor: 'None'
},
{
id: uuid(),
firstName: 'Jane',
lastName: 'Doe',
favColor: 'None'
}
]
}
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
render() {
return (
<div>
{this.state.users.map((user) => {
return <div key={user.id}>
<h1>{user.firstName}</h1>
<h1>{user.lastName}</h1>
<form>
<select
name="favColor"
value={user.favColor}
onChange={() => this.handleChange(user.id)}
>
<option value="None" disabled>None</option>
<option value="Blue">Blue</option>
<option value="Red">Red</option>
<option value="Green">Green</option>
</select>
</form>
<h1>Fav Color: {user.favColor}</h1>
<hr />
</div>
})}
</div>
)
}
}
I expect the dropdowns to change state separately for each of the users
Your handleChange method is not accepting the correct arguments.
If you wish to update one user item in array you will need to create a new updated copy of the array and save back into state
handleChange = (e,id) => {
const updatedUser = {...this.state.users.find(x=>x.id ===id), favColor: e.target.value}
this.setState({
users: [...this.state.users.filter(x==>x.id!==id),updatedUser]
})
}
...
onChange={(e) => this.handleChange(e,user.id)}
To simplify mutations of state I can recommend taking a look at Immer
And as #JosephD rightly pointed out this won't mantain order so you will need to do something like this.state.users.map(u => u.id === id ? { ...u, favColor: e.target.value } : u)
Here is a codesandbox based on your code:
https://codesandbox.io/s/loving-cohen-do56l?fontsize=14
<select
name="favColor"
value={this.state.favColor}
onChange={(e) => this.handleChange(e)}> // first Change
handleChange = (e) => {
this.setState({
favColor: e.target.value
})
} // Second Change
This will work for you
You are updating the state the wrong way;
Your state:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
Your update / intention:
users: [
{ id: 1, ... },
{ id: 1, ... }
]
favColor: color // intention, because you don’t pass event
To update the right way, you need to:
pass the event and currentId, to handleChange, of the selected dropdown. Otherwise you cannot know the current user. Also, in your example you don’t pass the event, so you cannot retrieve the information of the dropdown. Causing the name of undefined error.
check when id of user matches with id of dropdown and change the value.
This example should work for you.
https://codesandbox.io/s/small-sea-mw08n
I have JSON file like this
[
{
"id": 1,
"country": "Afghanistan",
"city": ["Eshkashem","Fayzabad","Jurm","Khandud"]
},
{
"id": 2,
"country": "Italy",
"city": ["Milano","Rome","Torino","Venezia"]
}
]
and I want to iterate through array placed in the city. Idea is to have two selects, where the first select is reserved for countries and the second is reserved for cities. Whenever the user selects a country, I want to populate the second select with a list of cities. Problem is that I receive only one array of all cities for that country. Here is my code:
export default class DiffCountries extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
contacts: [],
selectedCountry: [],
selectedCity: []
}
}
onChangeHandler = (event) => {
const test = CountriesData[event.target.value - 1];
this.setState({
selectedCountry: test,
selectedCity: this.state.selectedCountry.city
})
console.log(this.state.selectedCity);
}
render() {
const { contacts } = this.state;
return (
<div>
<select name="" id="" onChange={this.onChangeHandler}>
{CountriesData.map(item => {
const { id, country } = item;
return <option key={id} value={id}>{country}</option>
})}
</select>
<select name="" id="">
{this.state.selectedCountry !== undefined ?
<option value="">{this.state.selectedCountry.city}</option> :
null
}
</select>
</div>
<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>
And here is the screenshot of my problem
Thank you in advance!
You need to use map() on the city array.
<select name = "" id = "" > {
this.state.selectedCountry !== undefined ?
this.state.selectedCountry.city.map((x,i) => <option value={x} key={i}>{x}</option>)
:null
}
</select>
You need to iterate through the array.
this.state.selectedCountry.city.map((city, index) => {
return <option value={city} key={index}>{city}</option>
})
Be aware, that using the index as a key is considered an anti pattern. You could use the name of the city as a key as well. E.g.:
this.state.selectedCountry.city.map(city => {
return <option value={city} key={city}>{city}</option>
})
edit to add link to mdn docs as suggested in comments: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Example:
const CountriesData = [
{
id: 1,
country: 'Afghanistan',
city: ['Eshkashem', 'Fayzabad', 'Jurm', 'Khandud'],
},
{
id: 2,
country: 'Italy',
city: ['Milano', 'Rome', 'Torino', 'Venezia'],
},
];
class DiffCountries extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedCountry: null,
};
}
onChangeHandler = event => {
const selectedCountry = CountriesData[event.target.value - 1];
this.setState({
selectedCountry,
});
};
render() {
const { selectedCountry } = this.state;
return (
<div>
<select
name="country"
defaultValue="country"
onChange={this.onChangeHandler}
>
<option disabled value="country">
Select country
</option>
{CountriesData.map(({ id, country }) => (
<option key={id} value={id}>
{country}
</option>
))}
</select>
{selectedCountry && (
<select name="city" defaultValue="city">
<option disabled value="city">
Select city
</option>
{selectedCountry.city.map(item => (
<option key={item} value={item}>
{item}
</option>
))}
</select>
)}
</div>
);
}
}
ReactDOM.render(<DiffCountries />, document.getElementById('container'));
What I have here is a function where I call the codigo, and the nombre, in the DB
table registrations. What I want to achieve is that the digital code that is like an autocomplete to fill in the name when you select the code.
enter image description here
class Matriculas extends Component {
state = {
status: "initial",
data: []
}
componentDidMount = () => {
this. getInfo()
}
getInfo= async () => {
try {
const response = await getAll('matriculas')
console.log(response.data)
this.setState({
status: "done",
data: response.data
});
} catch (error) {
this.setState({
status: "error"
});
}
};
render() {
const data = [...this.state.data];
return (
<Container>
<RowContainer margin="1px" >
<ColumnContainer margin="10px">
<h3>Info</h3>
<label>Codigo</label>
<Input
width='150px'
type="text"
placeholder="Digite el codigo"
value={data.codigo } ref="codigo" />
<label>Nombre</label>
<Input
width='150px'
type="text"
placeholder="Nombre completo"
value={data.nombre} />
</ColumnContainer>
</RowContainer>
</Container>
)
}
};
export default Matriculas;
What you most likely want to use is react-select
You can pass options to the select (which would be your names) and it will return values that match whatever you type in the search bar.
import Select from 'react-select'
const options = [
{ value: 'mike', label: 'Mike' },
{ value: 'john', label: 'John' },
{ value: 'vanessa', label: 'Vanessa' }
]
const MyComponent = () => (
<Select options={options} />
)
So you can take that example, and the examples in the link, and put it in your code:
import Select from 'react-select'
<Container>
<RowContainer margin="1px" >
<ColumnContainer margin="10px">
<h3>Info</h3>
<label>Codigo</label>
<Input
width='150px'
type="text"
placeholder="Digite el codigo"
value={data.codigo } ref="codigo" />
<label>Nombre</label>
<Select
value={this.state.nameValue}
onChange={event => {this.setState({nameValue: e.value})}
options={options} />
</ColumnContainer>
</RowContainer>
</Container>
When using onChage, it returns an event, which has the value of the selected name. You can use that to set the state's nameValue, and then use that name value in the rest of your component as well
Once you get this up and running, it also worth looking at the async select, which allows you to give an async function that returns values (your getInfo function, for example)
-- edit --
If you want to define the onChange event elsewhere, it would look like this:
handleChange = event => {
// event.value will be the value of the select
this.setState({optionSelected: event.value});
}
and then in your onChange, tell it that is the function you want but do not invoke it (don't write it with parentheses):
<Select
value={this.state.optionSelected}
onChange={this.handleChange}
options={options} />
The example code in the react-bootstrap site shows the following. I need to drive the options using an array, but I'm having trouble finding examples that will compile.
<Input type="select" label="Multiple Select" multiple>
<option value="select">select (multiple)</option>
<option value="other">...</option>
</Input>
You can start with these two functions. The first will create your select options dynamically based on the props passed to the page. If they are mapped to the state then the select will recreate itself.
createSelectItems() {
let items = [];
for (let i = 0; i <= this.props.maxValue; i++) {
items.push(<option key={i} value={i}>{i}</option>);
//here I will be creating my options dynamically based on
//what props are currently passed to the parent component
}
return items;
}
onDropdownSelected(e) {
console.log("THE VAL", e.target.value);
//here you will see the current selected value of the select input
}
Then you will have this block of code inside render. You will pass a function reference to the onChange prop and everytime onChange is called the selected object will bind with that function automatically. And instead of manually writing your options you will just call the createSelectItems() function which will build and return your options based on some constraints (which can change).
<Input type="select" onChange={this.onDropdownSelected} label="Multiple Select" multiple>
{this.createSelectItems()}
</Input>
My working example
this.countryData = [
{ value: 'USA', name: 'USA' },
{ value: 'CANADA', name: 'CANADA' }
];
<select name="country" value={this.state.data.country}>
{this.countryData.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>;
})}
</select>
bind dynamic drop using arrow function.
class BindDropDown extends React.Component {
constructor(props) {
super(props);
this.state = {
values: [
{ name: 'One', id: 1 },
{ name: 'Two', id: 2 },
{ name: 'Three', id: 3 },
{ name: 'four', id: 4 }
]
};
}
render() {
let optionTemplate = this.state.values.map(v => (
<option value={v.id}>{v.name}</option>
));
return (
<label>
Pick your favorite Number:
<select value={this.state.value} onChange={this.handleChange}>
{optionTemplate}
</select>
</label>
);
}
}
ReactDOM.render(
<BindDropDown />,
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">
<!-- This element's contents will be replaced with your component. -->
</div>
// on component load, load this list of values
// or we can get this details from api call also
const animalsList = [
{
id: 1,
value: 'Tiger'
}, {
id: 2,
value: 'Lion'
}, {
id: 3,
value: 'Dog'
}, {
id: 4,
value: 'Cat'
}
];
// generage select dropdown option list dynamically
function Options({ options }) {
return (
options.map(option =>
<option key={option.id} value={option.value}>
{option.value}
</option>)
);
}
<select
name="animal"
className="form-control">
<Options options={animalsList} />
</select>
Basically all you need to do, is to map array. This will return a list of <option> elements, which you can place inside form to render.
array.map((element, index) => <option key={index}>{element}</option>)
Complete function component, that renders <option>s from array saved in component's state. Multiple property let's you CTRL-click many elements to select. Remove it, if you want dropdown menu.
import React, { useState } from "react";
const ExampleComponent = () => {
const [options, setOptions] = useState(["option 1", "option 2", "option 3"]);
return (
<form>
<select multiple>
{ options.map((element, index) => <option key={index}>{element}</option>) }
</select>
<button>Add</button>
</form>
);
}
component with multiple select
Working example: https://codesandbox.io/s/blue-moon-rt6k6?file=/src/App.js
A 1 liner would be:
import * as YourTypes from 'Constants/YourTypes';
....
<Input ...>
{Object.keys(YourTypes).map((t,i) => <option key={i} value={t}>{t}</option>)}
</Input>
Assuming you store the list constants in a separate file (and you should, unless they're downloaded from a web service):
# YourTypes.js
export const MY_TYPE_1="My Type 1"
....
You need to add key for mapping otherwise it throws warning because each props should have a unique key. Code revised below:
let optionTemplate = this.state.values.map(
(v, index) => (<option key={index} value={v.id}>{v.name}</option>)
);
You can create dynamic select options by map()
Example code
return (
<select className="form-control"
value={this.state.value}
onChange={event => this.setState({selectedMsgTemplate: event.target.value})}>
{
templates.map(msgTemplate => {
return (
<option key={msgTemplate.id} value={msgTemplate.text}>
Select one...
</option>
)
})
}
</select>
)
</label>
);
I was able to do this using Typeahead. It looks bit lengthy for a simple scenario but I'm posting this as it will be helpful for someone.
First I have created a component so that it is reusable.
interface DynamicSelectProps {
readonly id: string
readonly options: any[]
readonly defaultValue: string | null
readonly disabled: boolean
onSelectItem(item: any): any
children?:React.ReactNode
}
export default function DynamicSelect({id, options, defaultValue, onSelectItem, disabled}: DynamicSelectProps) {
const [selection, setSelection] = useState<any[]>([]);
return <>
<Typeahead
labelKey={option => `${option.key}`}
id={id}
onChange={selected => {
setSelection(selected)
onSelectItem(selected)
}}
options={options}
defaultInputValue={defaultValue || ""}
placeholder="Search"
selected={selection}
disabled={disabled}
/>
</>
}
Callback function
function onSelection(selection: any) {
console.log(selection)
//handle selection
}
Usage
<div className="form-group">
<DynamicSelect
options={array.map(item => <option key={item} value={item}>{item}</option>)}
id="search-typeahead"
defaultValue={<default-value>}
disabled={false}
onSelectItem={onSelection}>
</DynamicSelect>
</div>