check atleast three options is selected on form submission in ReactJS - javascript

I am working on reactjs, I am using ant.design selection module to select different skill from drop down. I want to check atleast three options is selected on form submission. I will also share code with you Can you guys help me how I can solve this problem
<FormItem>
{getFieldDecorator('skills', {
rules: [
{
required: true,
message: 'Please select skillset!',
},
],
})(
<Select
mode="tags"
style={{ width: '100%' }}
defaultValue={defaultSkills}
>
{children}
</Select>,
)}
</FormItem>

Since you are using antd, there is an option for multi selection : mode="multiple" then you will have a array of selected values in your handleChange method which you can check whatever conditions you need. Here is an example :
const Option = Select.Option;
const children = [];
for (let i = 10; i < 36; i++) {
children.push(<Option key={i.toString(36) + i}>{i.toString(36) + i}</Option>);
}
function handleChange(value) {
// Value is a array of selected values ["a10", "f15", "g16"]
console.log(value);
// Do rest of your code here
}
ReactDOM.render(
<Select
mode="multiple"
style={{ width: '100%' }}
placeholder="Please select"
onChange={handleChange}
>
{children}
</Select>,
document.getElementById('container')
);
https://codesandbox.io/s/pk6nmk560

Related

How do I add select options from an object

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>
);
}

Update Select Option list based on other Select field selection ant design

<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>

How to set value dynamically inside Form.List using setFieldsValue in Antd 4?

<Form>
<Form.List name="projects">
{fields =>
...
fields.map(field => <Form.Item name={[field.key, "type"]} hidden={true} initialValue={type} />)
...
}
</Form.List>
</Form>
Whenever type changes, I want to set Item value dynamically.
I think I can get form instance using the useForm and use the setFieldsValue like below.
form = useForm()
onChange(type) {
form.setFieldsValue(/* values here */)
}
But I am not sure how to use the form.setFieldsValue to refer item inside Form.List.
Who can give me solution?
The Antd form list creates an array of item objects. Then it is necessary to locate the position of the object within the array that contains the item that will have the value changed.
Return all values for form items:
const fields = form.getFieldsValue()
Retrieve the array containing the form list objects:
const {projects} = fields
Pass the object's position within the array and change the item to the new value:
Object.assign(projects[key], {type: value})
Then pass the new array value to the form:
form.setFieldsValue({projects})
Here is a complete example:
import React from 'react';
import { Form, Select, Button } from 'antd'
const { Option } = Select
export default function FormProject() {
const [ form ] = Form.useForm()
const onChange = (value, key) => {
const fields = form.getFieldsValue()
const { projects } = fields
Object.assign(projects[key], { type: value })
form.setFieldsValue({ projects })
}
return (
<Form
form={form}
initialValue={{type: "type_1"}}
>
<Form.List name="projects">
{(fields, { add, remove }) => (
<div>
<Form.Item>
<Button type="dashed" onClick={add} >
Add
</Button>
</Form.Item>
{fields.map(field => (
<div>
<Form.Item name={[field.key, "type"]} hidden={true} />
<Form.Item noStyle name={[field.name, 'id']}>
<Button type="dashed" onClick={remove} >
Remove
</Button>
</Form.Item>
<Form.Item
{...field}
name={[field.name, 'type_project']}
label="Tipo"
fieldKey={[field.fieldKey, 'type_project']}
>
<Select onChange={e => onChange(e, field.key)}>
<Option value="type_1">Type 1</Option>
<Option value="type_2">Type 2</Option>
<Option value="type_1">Type 2</Option>
</Select>
</Form.Item>
</div>
))}
</div>
)}
</Form.List>
</Form>
);
}
One of the ways to manage those nested values is to get the whole value and change the attribite you need and then pass it to your form:
const value = form.getFieldValue(field.key)
form.setFieldsValue({[field.key]: {...value, ['type']: your_new_value}})
I made it without mutation. Because mutation did not refresh updated field.
just an idea:
const onChange = (value, originalProject) => {
const projects = form.getFieldsValue('projects')
const updatedProjects = projects.map(project => {
if (project.id === originalProject.id) {
return {
...project,
updateKey: value
}
}
return project;
})
form.setFieldsValue({ projects: updatedProjects })
}
This worked for me.
const data = form.getFieldValue('projects');
data[key] = { ...data[key], [property name]: 'new property value' };
form.setFieldValue('projects', data);
I hope this helps you or anyone reading this. Happy coding!

Display input fields based on a variable number in state

I have the below piece of code. I have defined a const in state called items. This is the dropdown selection. It has value 0, 1 and 2 and these indicate no. of dependents.
Once user selects this dropdown, i then update the value of selected dropdown in dependents constant using useState.
I want to display input field below this which will allow user to enter dependents age. For e.g. user selects 2 in dropdown. I want to show 2 input fields below which will allow user to enter the age of 2 dependents. Similarly, if i select 1- i should only show 1 input field. In that way it should show dynamically the number of input fields as user selects in dropdown.
Can someone please let me know how to achieve it.
function App() {
const [items] = useState([
{ label: "0", value: "0" },
{ label: "1", value: "1" },
{ label: "2", value: "2" }
]);
const [dependents, setDependents] = useState("0");
return (
<div className="App">
Select Dependents:
<select onChange={e => setDependents(e.currentTarget.value)}>
{items.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))
}
</select>
</div>
);
}
"
Create an array based on the selected value and map it to render the input:
<div className="App">
Select Dependents:
<select onChange={e => setDependents(e.currentTarget.value)}>
{items.map(({ label, value }) => (
<option key={value} value={value}>
{label}
</option>
))
}
</select>
{[...Array(+dependents)].map((_,index)=>{
return <input type="text" key={index} />
})
}
</div>
Note :
[...Array(number)] creates an array of undefined items with length equals to number, and +number converts a string to number like +"4" gives 4

Ant Design reset select

I have 2 <Select>'s. The values in the second are dependant on the selection made on the first. When I change the selected item in the first, the available options on the second update. But if I already have a selection made on the second, that option remains selected even if it isn't supposed to be available based on a change to the first select.
How can I reset the second select to have nothing selected when a change is made to the first select?
First Select:
<FormItem {...formTailLayout}>
<FormTitle>Operation</FormTitle>
{getFieldDecorator('Operation', {
rules: [
{
required: true
}
]
})(
<Select
showSearch
placeholder="Select an option"
onChange={this.handleOperationChange}
>
{operations.map(operation => (
<Option value={operation.operation_id}>
{operation.operation_name}
</Option>
))}
</Select>
)}
</FormItem>
Second Select:
<FormItem {...formTailLayout}>
<FormTitle>Metric</FormTitle>
{getFieldDecorator('Metric', {
rules: [
{
required: true
}
]
})(
<Select
showSearch
placeholder="Select an operation first"
onChange={this.handleMetricChange}
>
{matrics
.filter(
metric => metric.operation_fk === operation_fk
)
.map(metric => (
<Option value={metric.metric_name}>
{metric.metric_name}
</Option>
))}
</Select>
)}
</FormItem>
You need to take a look at Coordinated Controls example mentioned on ant-design page. You can simply use setFieldsValue in your first onChange method to set the value of second select field.
handleOperationChange = () => {
this.props.form.setFieldsValue({
Metric: undefined
})
}
I have created a sandbox demo.
In antd, In Class Component, Using Ref, Clear or reset select list value or form item value
add formRef = React.createRef(); just below class component, like:
export default class TestClass extends Component { formRef = React.createRef(); }
add ref={this.formRef} in <Form /> component like this <Form ref={this.formRef}/>
add this line on btn click or in any function you want
this.formRef.current.setFieldsValue({ network: undefined });
Here network in above step is the name of form item
<Form.Item name="network" label="Network"></Form.Item>
Inside handleOperationChange method use resetfileds, this gonna reset your second select box.
this.props.form.resetFields('Metric');
<Select
className="mt-3"
style={{ width: "100%" }}
placeholder="Select your Sub Category"
onChange={handleChangeSubCategory}
disabled={categoryGroup.category === null}
value={categoryGroup.subcategory || undefined}
>
{subCategory.map(item => (
<Option key={item} value={item} label={item}>
<div>
<Avatar style={{ background: "#10899e" }}>
{item[0]}
</Avatar>{" "}
{item}
</div>
</Option>
))}
</Select>
If functional component is used, then we can use Form:
const [form] = Form.useForm()
and it is possible to clear value like this:
const someMethodToClearCurrentSelection = () => {
// ... the code is omitted for the brevity
form.setFieldsValue({ fooProduct: undefined })
}
and our control looks like this:
<Form form={form} name="basic" onFinish={onFinish} layout="vertical">
<Form.Item
key="fooProduct"
name="fooProduct"
label="Some product"
className={s.fooContainer}
rules={[
{
required: true,
message: `Field 'Some produc' is required`,
},
]}
>
<Select
showSearch
optionFilterProp="children"
filterOption={(input, option) =>
option?.children.toLowerCase().includes(input.toLowerCase())
}
allowClear
onChange={handleMarkClick}
>
{products.map((option) => (
<Option key={option.id} value={option.id}>
{option.name}
</Option>
))}
</Select>
</Form.Item>
Read more in antd docs here and examples while migrating from v3 to v4
You can make use of the useEffect() hook.
Define a useForm() custom hook fir your form.
Example: const [form] = Form.useForm();`
Wrap your 1st and 2nd select boxes inside the form and use <Form.Item> to define the input on the form. And whenever the first select's input is changed, clear the 2nd select values in useEffect() hook.
Example:
useEffect(() => {
form.setFieldsValue({
marketplace: [] //2nd select name
});
}, [selectedRegion]); //first select's value
This work perfect for me.

Categories

Resources