I am learning react and struggling to get a second dropdown to populate based on which option is clicked from first dropdown.
I have included my code below.
I think the issue is where I try to set this.state.selected = param.tableName. I don't think that will work, but I'm not sure what to use instead.
I need an onClick event to change the this.state.selected variable to the option that is selected I think, and then check when tableName === selected. I will also include my JSON below so the context is more clear.
import React from 'react';
import axios from 'axios';
class Search extends React.Component {
constructor(props) {
super(props)
this.state = {
params: [],
columnParams: [],
selected: ''
}
}
componentWillMount() {
axios.get('http://localhost:3010/api/schema')
.then( response => {
this.setState({params: response.data})
})
}
render() {
return (
<div className="Search">
<select>{this.state.params.map((param, i) =>
<option key={i} onClick={this.setState(this.state.selected ={param.tableName})}>
{param.tableName}
</option>)}
</select>
<select>
{
this.state.params
.filter(({tableName}) => tableName === selected)
.map(({columns}) => columns.map((col) =><option>{col}</option>))}
[
{
"tableName": "customer",
"columns": [
"customerid",
"title",
"prefix",
"firstname",
"lastname",
"suffix",
"phone",
"email"
]
},
{
"tableName": "product",
"columns": [
"productid",
"name",
"color",
"price",
"productadjective",
"productmaterial"
]
},
You're doing it wrong way. You cannot call onClick method on <option>. Instead you should use onChange method on <select> (see react docs) like this:
<select onChange={this.handleChange} >
<option>1</option>
<option>2</option>
<option>3</option>
</select>
Then on 'onChange' event you can set your state to the selected option. Let's understand this scenario with an example.
Suppose you have two dropdowns. One for showing companies and one for jobs corresponding to each company. Each company has its own set of jobs like this:
companies:[
{ name: 'company1', jobs: ['job1-1', 'job1-2', 'job1-3']},
{ name: 'company2', jobs: ['job2-1', 'job2-2', 'job2-3']},
{ name: 'company3', jobs: ['job3-1', 'job3-2', 'job3-3']}
]
Now you have to just set a state, lets say 'selectedCompany' and then filter the companies array by 'selectedCompany'. Then you can just get that particular company object and map your 'jobs' dropdown by iterating over jobs array inside the company object.
Here is the code snippet:
https://codepen.io/madhurgarg71/pen/pRpoMw
event handlers for e.g. onClick must be a function
this.setState expects an object, which will be merged with the state
so the part where you set selected in your state must be
<option key={i} onClick={() => {
this.setState({selected: param.tableName})
}>{param.tableName}}}</option>
you use an undefined variable in your filter (selected),
you have to use .filter(({tableName}) => tableName === this.state.selected) instead
Some things to look at:
The syntax in your call to setState in your <option> onClick event handler looks incorrect.
You also make try to reference this.state.selected in your .filter condition without the path to the variable (this.state)
Suggested solution(s):
// The .setState method expects an object to be passed to it rather
// than an expression that mutates the state directly
// Pass it an object containing the properties you want to change,
// ensure your use colon (':') rather than equals sign ('=') to set property:
<option key={i} onClick={
this.setState({
selected: param.tableName
})
</option>
// In the <select> tag,
// you need to refer to the selected property in it as this.state.selected:
// Don't forget the closing select tag, it's missing from your snippet
<select>
{
this.state.params
.filter(tableName => (tableName === this.state.selected))
.map(columns => columns.map(col => <option>{col}</option>))
}
</select>
This issue can easily be dealt with by chaining a filter and map together on the second input, which filters it's options depending on the input of the first drop down.
<select value={country} onChange={e => setCountry(e.target.value)}>
{countries.map(country => (
<option value={country}>{country}</option>
))}
</select>
<select value={school} onChange={e => setSchool(e.target.value)}>
{schools.filter(school => school.Country === country).map(school => (
<option value={school.Name}>{school.Name}</option>
))}
</select>
Above we have two selects. The first selects a value, and then the options for the second will render based on this.
Related
I am trying to handle categories and sub-categories in react select hook form field. Currently we pass an array to options and it renders it.
Now I have data in arrays and nested arrays.
like this
Category: [
{
Sub-Category: []
},
{
Sub-Category: []
}
]
Currently, I am handling it like this
<option value="">Select</option>
{medicalCategory?.response?.data?.map((value, index) => (
<optgroup label={value?.cpt_category}>
{value?.sub_categories?.map((subCategory, index) => (
<option value={subCategory?.id}>
{subCategory?.cpt_category}
</option>
))}
</optgroup>
))}
But I want to this in react select hook form. And when select a value, i should also get the value of category. Like this value: {category { Sub-Category: [] }}
I tried using onClick, onChange. But onChange only provide the value of the selected sub-category. Like only name or id. I want whole parent-Category and sub-category And as we know onClick doesn't work on onClick.
I'm trying to loop through the below data structure and set 'Select' fields options. This is where I have an issue. I'm trying to assign 'nameCombined' & 'codeCombined' to the value and text of the Select Form field.
DataStructure:
{
"Bucks":{
"countyCode":"42017",
"globalStateCode":"PA",
"stateCode":"PA",
"nameCombined":"42017 (PA)",
"codeCombined":"42017 PA Bucks"
},
"Montgomery":{
"countyCode":"42091",
"globalStateCode":"PA",
"stateCode":"PA",
"nameCombined":"42091 (PA)",
"codeCombined":"42091 PA Montgomery"
}
}
React JSX
Select Component
const {
name,
options,
actions: { handleFieldChange },
} = props;
<select id={name} onChange={(e) => handleValueChange(e)}>
{options.map((option, idx) => (
<option key={`${option.value}_${idx}`} value={option.value}>
{value === option.text}
</option>
))}
</select>;
// The component that consumes Select Component
// ============================================
<Select
inputData={{
name:name,
options:Object.entries(counties).map(([key, item]) => ({'value': item.nameCombined, 'text': item.codeCombined})), // <- this line needs attention
type:INPUT_TYPES.DROPDOWN,
}},
actions={{
handleFieldChange: handleDropdownChange
}}
/>
Try Object.keys instead of Object.entries.
Object.keys(counties).map(key => ({value: counties[key].nameCombined, item: counties[key].codeCombined}))
The conceptual limitation that you have in your assumption is that you need a proper array to iterate and Object.entries gives you [[key1, object1],...] while Object.keys gives you [key1, key2].
I have a perplexing question. I am receiving a payload from an axios call that I need to populate in a select (dropdown) box in React--except the payload I receive is an array of arrays. There is a large array of (2) Two element arrays received, the two elements being a shorthand ID name, and the fullname. I need to concatenate these two values together into one option. For instance the structure of the data received:
{
"groups": [
[
"short_name_id",
"longer_name"
]
]
The data in the select box should be a concatenation of the two elements like: "short_name_id - longer_name" I don't think I have to manually concatenate both the two values together and then put in state, but to show each of them together in each select dropdown choice.
In the call I have an axios call that returns the data correctly:
componentDidMount() {
axios.get('https://someurl.com/api/groups')
.then(response => {
console.log(response);
this.setState({ data: response.data });
})
.catch(error => console.log(error.response));
}
In the render() I set the props to the data object returned but the select box doesn't populate?
Not sure how to get the two array elements to show up as one entry and populate the state into the select box? I am not accessing/referencing the data that is in state properly considering the array structure I don't think?
render() {
const { groups } = this.props;
return (
<label>
Group:
<select onChange={this.handleInputChange}>
{groups &&
groups.length > 0 &&
groups.map(group => {
return <option key={group} value={group}>{group}</option>;
})}
</select>
</label>
);
}
render() {
const { groups } = this.props;
return (
<label>
Group:
<select onChange={this.handleInputChange}>
{groups &&
groups.length > 0 &&
groups.map(group => {
return <option key={group[0]} value={group[0]}>{group[0]} - {group[1]}</option>;
})}
</select>
</label>
);
}
This should work, but personally I wouldn't store the data as array of arrays. I would rather store them as array of objects, so instead of looking like this:
[
"short_name_id",
"longer_name"
]
It would look like this:
{
"short_name_id": "value here",
"longer_name": "value here"
}
This way your return statement instead of looking like this:
return <option key={group[0]} value={group[0]}>{group[0]} - {group[1]}</option>;
could look like this:
return <option key={group.short_name_id} value={group.short_name_id}>{group.short_name_id} - {group.longer_name}</option>;
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>
I'm using react-select to create a Select option in my create-react-app and am trying to map over an array of objects to generate the options. My app loads fine but when I click on the Select I get this error: Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {name}). If you meant to render a collection of children, use an array instead.
I'm passing the data to the component via props which is working fine, and the data is structured like this:
const guests = [
{
name: 'Kait',
plus: true,
plusName: 'Kitty'
},
{
name: 'Séanin',
plus: true,
plusName: 'Guest'
}
]
And here's the Select component:
<Select
value={selectedOption}
onChange={this.handleChange}
options={
this.props.guests.map((guest, index) => {
return {
label: guest,
value: guest,
key: index
}
})
}
/>
Any ideas on how I can fix this?
Sung M. Kim‘s answer is correct, but there is an easier way to use your attributes as label and value without remapping your options array.
Using the props getOptionLabel and getOptionValue you can keep your object mappings. Both accept a function that gets a single option as an argument and returns the value or label from the appropriate object property as string.
<Select
options={this.props.guests}
getOptionLabel={(option) => option.name}
{ /* Couldn't find a value in your structure, so I used name again */ }
getOptionValue=((option) => option.name}
{ ... }
/>
See documentation for more.
You probably will have to generate the array before rendering the component
const options = this.props.guests.map((guest, index) => {
return {
label: guest.name,
value: guest,
key: index
}
})
<Select
value={selectedOption}
onChange={this.handleChange}
options={options}
/>
Edit:
is because you are passing an object in the label field. You should pass a String instead
The error occurs because the label is set as guest (an object) not as guest.name (a string).
Making following change will work.
<Select
value={selectedOption}
onChange={this.handleChange}
options={
this.props.guests.map((guest, index) => {
return {
- label: guest,
+ label: guest.name
value: guest,
key: index
}
})
}
/>
You can try it out in the sandbox link below.