I am trying to create a dropdown list based on a object I have. I can't think of a way to do it that's not completely convoluted and the solutions I have come up with don't work either. I'm new to react and stackoverflow so I am not sure if just asking for advice is seen as okay. Forgive me if it's not. I am building it with React. Is there a way to do this that I am missing. Again sorry if you are not supposed to ask for help here
I have created the component as follows
const HardwearForm = ({isActive, Products, outputObj, setOutputObj}) => {
const handleSelect = e => {
let newParentNode = e.target;
let selectedNumber = newParentNode.options.selectedIndex;
let choice = newParentNode.options[selectedNumber].value;
setOutputObj(outputObj[newParentNode.id] = choice);
console.log(outputObj)
}
useEffect(() => {
let parentNode = document.getElementById("make");
Object.keys(Products[isActive]["hardwear"]).forEach(key => {
let newOption = document.createElement('option');
newOption.value = key;
newOption.innerText = key;
parentNode.appendChild(newOption);
})
},[isActive, Products]);
return (
<div>
<select name="make" className="hardwear" id="make" onChange={handleSelect}>
<option value="*">Manufacturer</option>
</select>
<select name="model" className="hardwear" id="model"></select>
{isActive === "handset"|| isActive==="tablet"?
<>
<select name="storage" className="hardwear" id="storage""></select></>:""}
{isActive === "handset"|| isActive==="tablet" ||isActive=== "watch"?
<>
<select name="color" className="hardwear"></select></>:""}
{isActive === "handset"|| isActive==="watch"?
<>
)
The basic idea is I have a menu with a few buttons. Those buttons update the isActive state, which then causes the useEffect to run and populate the first select with the options. That works fine. I've then set it up so once a selection in the first dropdown is chosen it fires the handleSelect function, which does the same thing for the next select. I've set up a empty object to store all the options. setOutputObj(outputObj[newParentNode.id] = choice); ands a key value pair to that object. The problem is, if you then try and change the selected option it errors out "TypeError: Cannot create property 'make' on string 'Huawei'".
Here is an example of the date structure I am using.
"Fairphone": {
"3+": {
"color": ["Black"],
"storage": ["64GB"],
}
},
"Doro": {
"8050": {
"color": ["Black"],
"storage": ["16GB"],
}
},
"Mobiwire": {
"Smart N12": {
"color": ["Dark Metallic Blue"],
"storage": ["16GB"],
}
},
I'm a bit confused about your data structure and what you are trying to achieve, but in general, to dynamically set options of a select input from an object I think it's better to use array.map rather than to try to modify the DOM directly.
For example:
https://codesandbox.io/s/compassionate-buck-8sir5?file=/src/App.js
import { useState } from "react";
import "./styles.css";
const productsByManufacturers = {
"Manufacturer 1": ["product 1", "product 2", "product 3"],
"Manufacturer 2": ["product 4", "product 5"]
};
export default function App() {
const [manufacturer, setManufacturer] = useState("");
return (
<div className="App" style={{ display: "flex", flexDirection: "column" }}>
<label>Select Manufacturer:</label>
<select
value={manufacturer}
onChange={(e) => setManufacturer(e.target.value)}
>
<option value="" disabled>
Select a manufacturer
</option>
{Object.keys(productsByManufacturers).map((manufacturer) => (
<option value={manufacturer}>{manufacturer}</option>
))}
</select>
{manufacturer !== "" && (
<div
style={{ marginTop: 20, display: "flex", flexDirection: "column" }}
>
<label>Select Product:</label>
<select>
{productsByManufacturers[manufacturer].map((product) => (
<option value={product}>{product}</option>
))}
</select>
</div>
)}
</div>
);
}
Related
I am trying to change the background color of select element based on which option user selects. I have created this example https://codepen.io/lordKappa/pen/YzrEWXd and here the color changes but it also changes for all select elements not just for the one we changed.
const [selectState, setSelectState] = useState("To-do");
const [taskID, setTaskID] = useState();
function handleDropdownChange(e) {
var taskID = e.currentTarget.getAttribute("data-index");
setTaskID(taskID);
setSelectState(e.target.value);
}
if (selectState === "To-do") {
selectStyle = {
backgroundColor: "red",
};
} else if (selectState === "Done") {
selectStyle = {
backgroundColor: "green",
};
} else {
selectStyle = {
backgroundColor: "yellow",
};
}
return (
<div className="box">
<div>
{elementList.map((task) => (
<div key={task.id}>
<h1>{task.info}</h1>
<select
onChange={handleDropdownChange}
data-index={task.id}
style={selectStyle}
>
<option value="To-do">To-do</option>
<option value="Done">Done</option>
<option value="In progress">In progress</option>
</select>
</div>
))}
</div>
</div>
);
};
The color changes an all dropdowns in the code-pen you provided because they all rely on the same piece of state. You either need to make the state for each dropdown be independent of each other, or separate the dropdown into its own component.
You should create a state for tasks
const [tasks, setTasks] = useState([
{ id: 0, info: "Task 1", status: 'TODO' },
{ id: 1, info: "Task 2", status: 'TODO' },
{ id: 2, info: "Task 3", status: 'TODO' }
]);
Then, everytime you change the selectbox, update status inside tasks state
function handleDropdownChange(e) {
var taskID = e.currentTarget.getAttribute("data-index");
setTasks(tasks.map(task => {
if (taskID !== task.id) return task;
task.status = e.target.value
return task
}));
}
Finally, when rendering tasks we only need to check the task's status and change style
{elementList.map((task) => {
// put your condition here ....
return (
<div key={task.id}>
<h1>{task.info}</h1>
<select
onChange={handleDropdownChange}
data-index={task.id}
style={selectStyle}
>
<option value="To-do">To-do</option>
<option value="Done">Done</option>
<option value="In progress">In progress</option>
</select>
</div>
)
})}
I'm using react-select library to add dropdown menu for my app, but "required" doesn't work (I read a discussion on GitHub for this library, creators haven't added that function yet), so for now how do I make select required? (I read here another post with the same problem, solution presented there didn't work for me). My code is:
import React, { useState } from "react";
import Select from "react-select";
export default function App() {
const [data, setData] = useState();
const options = [
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" },
{ value: "4", label: "4" }
];
const handleSubmit = (e) => {
console.log(data);
};
return (
<div className="App">
<form onSubmit={handleSubmit}>
<input required placeholder="name" />
<Select
options={options}
onChange={(e) => setData(e.value)}
value={options.filter(function (option) {
return option.value === data;
})}
label="Single select"
placeholder={"Select "}
menuPlacement="top"
required
/>
<button> Submit</button>
</form>
</div>
);
}
code sandbox
In my code I have a couple of regular inputs (where required works fine) but I want the Select also be required. Any suggestions are greatly appreciated.
I would think outside the box — in this case the Select component. What can you add that gets the same result? I took a quick stab at this for you:
const [isValid, setIsValid] = useState(false);
// This effect runs when 'data' changes
useEffect(() => {
// If there is data, the form is valid
setIsValid(data ? true : false);
}, [data]);
...
return (
<div>
<Select ... />
{!isValid && <p>You must choose a value</p>}
<button disabled={!isValid}>Submit</button>
</div>
)
https://codesandbox.io/s/vigilant-wiles-6lx5f
In this example, we check the state of the Select component — if it's empty (the user hasn't chosen anything) the form is invalid and the submit button is disabled.
<Form
layout="vertical"
size="medium"
className="test-form"
requiredMark={false}
onFinish={onFinish}
>
<Form.Item
name="companyId"
label="Company/Customer"
rules={[{ required: true, message: "Please select Company!"}]}
>
<Select
onChange={this.handleSelectCompanyOnchange}
style={{ width: "50%" }}
name="companyId"
>
{users.map((user, index) => {
return (
<Option key={index} value={user.companyID}>
{user.companyName}
</Option>
);
})}
</Select>
</Form.Item>
<Form.Item
label="Products"
name="products"
rules={[{ required: true, message: "Please select Products!"}]}
>
<Select mode="multiple" allowClear style={{ width: "70%" }}>
{products.map((product, index) => {
if (this.state.companyId == product.companyId) {
return (
<Option key={index} value={product.id}>
{product.productName}
</Option>
);
}
})}
</Select>
</Form.Item>
</Form>
I am trying to achieve Options in Products Select element changes according to the Company Select onChange selection.
I have specified onChange in Select and calling this.handleSelectCompanyOnchange. In which I get selected companyId.
In this.state.companyId I had set companyId manually which I will remove.
I am really new to ant design and not able to figure out how to update the Products list once Company is selected.
Here, users and products are json as below.
users:
[{
companyID: 2
companyName: "TEST1"
},{
companyID: 7
companyName: "TEST2"
}]
products:
[{
companyId: 2
id: 1
productName: "TESTProduct1"
},{
companyId: 7
productName: "TESTProduct2"
id: 2
},{
companyId: 7
id: 3
productName: "TESTProduct3"
},{
companyId: 7
id: 4
productName: "TESTProduct4"
}]
However, I have tried getValueFromEvent but not able to achieve this. I am using Ant design Form and Select for this. Also I did referred to https://github.com/ant-design/ant-design/issues/4862 and how to get field value on change for FormItem in antd
Here is what you need to achieve it.
Use onValuesChange prop of the Form. This is the best place to perform setState when it comes to antd Form field changes, not on Select or Input onChange.
<Form onValuesChange={handleFormValuesChange}>
...
</Form>
A form instance (hook). This is optional in your case, but this is useful on setting and getting form values. See more here about it.
const [form] = Form.useForm();
<Form form={form} onValuesChange={handleFormValuesChange}>
...
</Form>
This is the product options render looks like, a combination of map and filter where selectedCompanyId comes from state. Take note that don't use index as key if the fixed length of the list is unknown, the react will confuse on this and you will get some logical error. Use some unique id.
<Form.Item label="Products" name="product">
<Select>
{products
.filter((product) => product.companyId === selectedCompanyId)
.map((product) => (
<Option key={product.id} value={product.id}>
{product.productName}
</Option>
))}
</Select>
</Form.Item>
And here is the handleFormValuesChange
const handleFormValuesChange = (changedValues) => {
const formFieldName = Object.keys(changedValues)[0];
if (formFieldName === "company") {
setSelectedCompanyId(changedValues[formFieldName]); // perform setState here
form.setFieldsValue({product: undefined}) //reset product selection
}
};
Here is the complete working code in react hooks:
the idea here is that you only need to watch the value change in the state.
For example, your select should "watch" a value of state and then you can easily update the state via its own setState method of React. Inside of an onChange event, you can simply just do something with our state.
<Select
value={state.productId}
onChange={e => {// Do something}}
>
{...}
<Select/>
Below is my example code how did I update the day every time I reselect week selection. Hopefully that this can help you.
import { Divider, Select } from 'antd';
import React, { useState } from 'react';
export function Example(): JSX.Element {
const [state, setState] = useState<{week: number, day: number}>({
day: 1,
week: 1
});
const weeks = [1,2,3,4];
const days = [1,2,3,4,5];
return <>
<Select
value={state.week}
onChange={(value) => setState({ week: value, day: 1})}
>
{
weeks.map(week => {
return <Select.Option
key={week}
value={week}
>
{week}
</Select.Option>;
})
}
</Select>
<Divider/>
<Select
value={state.day}
onChange={(value) => setState({...state, day: value})}
>
{
days.map(day => {
return <Select.Option
key={day}
value={day}
>
{day}
</Select.Option>;
})
}
</Select>
</>;
}
Lets say you want to update your option list based on data you get from backend;
note:categories is an array object. From this array you take out label and value for each option, see below:
const newOptions = categories.map((item, index) => {
return {
label: item.name,
value: item._id,
};
});
Then use newOptions inside your form like that:
<Form.Item label="anyName" name="anyName">
<Select style={{ width: 220 }} onChange={ handleChange } options={ newOptions } />
</Form.Item>
i have a react-select component which i define like this:
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
/>
with opts = [{label: any, value:1}, {label:Two, value:2}].
The values when selected are stored in the state via portfolioSelector function. The problem is that when i select a value it wasn't show in the select field. My main component is this:
const PortfolioSelector = ({
opts,
portfolioSelector
}) => {
if (opts) {
return (
<div className="portfolio select-box">
<label htmlFor="selectBox" className="select-box__label">
Portfolio
</label>
<div className="select-box__container">
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
/>
</div>
<div className="separator" />
</div>
);
}
return (
<div>Loading</div>
);
};
Do you know why?
This is an alternative solution that i used.
Demo: https://codesandbox.io/s/github/mkaya95/React-Select_Set_Value_Example
import React, { useState } from "react";
import Select from "react-select";
export default function App() {
const [selectedOption, setSelectedOption] = useState("none");
const options = [
{ value: "none", label: "Empty" },
{ value: "left", label: "Open Left" },
{ value: "right", label: "Open Right" },
{
value: "tilt,left",
label: "Tilf and Open Left"
},
{
value: "tilt,right",
label: "Tilf and Open Right"
}
];
const handleTypeSelect = e => {
setSelectedOption(e.value);
};
return (
<div>
<Select
options={options}
onChange={handleTypeSelect}
value={options.filter(function(option) {
return option.value === selectedOption;
})}
label="Single select"
/>
</div>
);
}
<Select
options={this.props.locale}
onChange={this.selectCountryCode}
value={{label : this.state.selectedCountryLabel}}
/>
The value property expects the shape Array.
The value is handled really bad, and it needs hacks like here, explained here.
Long story short; the value works differently. You'd expect
value={MY_VALUE}, but it works instead
value={{label: MY_VALUE}}.
First thing is you are created the wrong array, if label: any or Two is string you have to add double quote.
Look at this:
opts = [{label: "any", value:1}, {label:"Two", value:2}]
Second, You must remember the options in this case is opts is an array of object which have label and value, what the data you want to add to your state?
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value.value /* or if you want to add label*/ value.label)}
placeholder="Select Portfolio"
/>
You can simply put the value property as Selected.label
value={selectedOption.label}
I fixed it.
You forgot add value property. Use this, check the working code:
const opts = [{ label: 'any', value: 1 }, { label: 'Two', value: 2 }];
const PortfolioSelector = ({ options }) => {
if (options) {
return (
<div className="portfolio select-box">
<label htmlFor="selectBox" className="select-box__label">
Portfolio
</label>
<div className="select-box__container">
<Select
id="portf"
options={options}
value={this.state.opts1}
onChange={value => this.setState({ opts1: value })}
placeholder="Select Portfolio" />
</div>
<div className="separator" />
</div>
);
}
return <div>Loading</div>;
};
and call your component
<PortfolioSelector options={opts} />
For any one who is facing problems with React-Select getting populated or React-select doesn't get selected value; try to give it a dynamic key attribute who changes every time your data changes (either options array or selected option), so it will re-render with new data.
this is my solution
Demo: https://codesandbox.io/s/prod-rgb-o5svh?file=/src/App.js
code:
import axios from 'axios'
import {useEffect, useState} from 'react'
import Select from 'react-select'
import "./styles.css";
export default function App() {
const [users, setUsers] = useState()
const [userId, setUserId] = useState()
useEffect(()=>{
const getUsers = async () => {
const {data} = await axios.get('https://jsonplaceholder.typicode.com/users')
setUsers(data)
}
getUsers()
},[])
const options = users && users.map(user =>{
return {label: user.name, value: user.id}
})
console.log(userId)
return (
<div className="App">
{users && (
<Select
placeholder='Select user...'
isSearchable
value={options.label}
options={options}
onChange={(option) => setUserId(option.value) }
/>
)}
</div>
)
}
Please provide lable and value object together like {label: ?, value: ?}
const selectedValue = options.find(x => x.value===value);
<Select
id="portf"
options={opts}
onChange={value => portfolioSelector(value)}
placeholder="Select Portfolio"
value={label : selectedValue.label, value: (value ? selectedValue.value : "")}
/>
opts = [{label: any, value:1}, {label:Two, value:2}]
value must be string.
I have this Dropdown menu instance:
<Dropdown
selection
options={this.state.options}
search
value={value}
onChange={this.handleChange}
onSearchChange={this.handleSearchChange}
/>
and when my backend returns response, which is then set as state and it is structured like this:
"options": [
{
"text": "New York,All Airports (NYC) , USA",
"value": "NYC"
},
{
"text": "New York,Newark Liberty Intl (EWR), USA",
"value": "EWR"
},
{
"text": "New York,John F Kennedy (JFK), USA",
"value": "JFK"
},
{
"text": "New York,La Guardia (LGA), USA",
"value": "LGA"
}
]
...I get this warning:
Warning: flattenChildren(...): Encountered two children with the same
key, 1:$BLZ. Child keys must be unique; when two children share a
key, only the first child will be used.
in select (created by Dropdown)
in div (created by Dropdown)
in Dropdown (created by SearchForm)
How do I add keys to these elements to prevent this warning?
So looking at the code for the Semantic UI source for the dropdown component, the render options function converts your passed in options into a array of DropdownItem components:
renderOptions = () => {
const { multiple, search, noResultsMessage } = this.props
const { selectedIndex, value } = this.state
const options = this.getMenuOptions()
if (noResultsMessage !== null && search && _.isEmpty(options)) {
return <div className='message'>{noResultsMessage}</div>
}
const isActive = multiple
? optValue => _.includes(value, optValue)
: optValue => optValue === value
return _.map(options, (opt, i) => (
<DropdownItem
key={`${opt.value}-${i}`}
active={isActive(opt.value)}
onClick={this.handleItemClick}
selected={selectedIndex === i}
{...opt}
// Needed for handling click events on disabled items
style={{ ...opt.style, pointerEvents: 'all' }}
/>
))
}
the key for this array is set by taking the value prop and appending the index to it:
key={`${opt.value}-${i}`}
which should always be unique since the index is used but there is another part of the code for hidden inputs
renderHiddenInput = () => {
debug('renderHiddenInput()')
const { value } = this.state
const { multiple, name, options, selection } = this.props
debug(`name: ${name}`)
debug(`selection: ${selection}`)
debug(`value: ${value}`)
if (!selection) return null
// a dropdown without an active item will have an empty string value
return (
<select type='hidden' aria-hidden='true' name={name} value={value} multiple={multiple}>
<option value='' />
{_.map(options, option => (
<option key={option.value} value={option.value}>{option.text}</option>
))}
</select>
)
}
in this one the key is set to only the value, not the value plus index.
<option key={option.value} value={option.value}>{option.text}</option>
this might be your problem, if you have duplicate values then the key will not be unique. Double check the options list to make sure you don't have duplicate values.