React form select element validation - reactjs

I have a form with a select element that has 5 options and this form requires one of those options to be selected by the user in order to be valid. I am trying to implement form validation such that, when the user clicks on the select element, if the user were to click away from the select element without selecting an option, the error should be set for that element indicating to the user that this field is required.
I have tried to implement validation onBlur by checking if the country value has been set in state but if the user selects a country, then when the onBlur validation runs, the state isn't yet updated and the error will display even though the user has selected an option. How do I go about correctly implementing this validation?
const countries = [
{ value: "AU", text: "Australia" },
{ value: "NZ", text: "New Zealand" },
{ value: "UK", text: "United Kingdom" },
{ value: "MY", text: "Malaysia" },
{ value: "SG", text: "Singapore" },
];
const Form = function (props) {
const validateInput = (e) => {
console.log("validating input");
if (!props.country) {
console.log("set the error");
}
};
return (
<>
<Select
options={countries}
value={props.country}
onChange={props.setCountry}
placeholder="Select..."
id="country"
name="country"
invalid={props.errors.country}
onBlur={validateInput}
/>
</>
)

Related

How can I handle error of array of objects with useformik, yup and material ui?

I have a form witch contains input as "firstName, lastName" etc.
When the user submit it the data will be send to the server with the following body
employee: [
{
key: "property",
key: "property",
},
],
manager: [
{
key: "property",
key: "property",
},
],
customer: [
{
name: "John",
},
{
name: "Doe",
},
]
Everything works fine, but I'm trying to improve my code for one of the input.
Basically to submit the form with success the user must enter at least one customer name in the "customer input" and then press the button "add customer".
After that I clean the input and render a list of "chip" with customer's name. The user can add as many customers as he wants.
Because I don't know how to handle a array of objects with useFormik, I did handle it myself with the following code:
const [customersList, setCustomersList] = useState<Array<Customer>>([]);
const [displayError, setDisplayError] = useState<Boolean>(false);
const [errorMessage, setErrorMessage] = useState<string>('');
const updateCustomerList = () => {
let customerName = capitalizeWords(formik.values.customer_name);
if (customerName.length === 0) {
setErrorMessage('Customer name is required');
return setDisplayError(true);
}
setCustomersList([...customersList, { name: customerName }]);
formik.setFieldValue('customer_name', '');
};
const isDuplicateCustomer = () => {
let customerName = capitalizeWords(formik.values.customer_name);
const isDuplicate = customersList.some(
(customer) => customer.name === customerName,
);
if (isDuplicate == true) {
setErrorMessage('Duplicate');
setDisplayError(true);
formik.setFieldValue('customer_name', '');
return true;
}
return false;
};
const onAddButtonClick = () => {
if (!isDuplicateCustomer()) {
updateCustomerList();
}
};
But now it's hard to handle errors on the customer's input with useFormik and yup, because I defined the "customer" input as string, and because I clean the input each time the user press the button 'add' it can't work.
My question is: is it possible to handle the "customersList" with useFormik or formik and get ride on "updateCustomerList" and "isDuplicateCustomer" by using native function of formik and yup validation ?
Because now the only way I found to submit the form is to handle the error myself and make the customer's input not required with yup.
Here is a codesandbox: https://codesandbox.io/s/quiet-tree-f26mte?file=/src/App.js

Semantic UI dropdown not setting value after selecting option

I am using React semantic ui. I am rendering a dropdown in Fieldset. I have written code such that, once a option is selected, the options is updated such that the selected option is removed from the list. But when I select an option from the dropdown, the selected value is not displayed, rather it shows empty.
Here is my code:
This is my dropdown code:
<Dropdown
name={`rows.${index}.mainField`}
className={"dropdown fieldDropdown"}
widths={2}
placeholder="Field"
fluid
selection
options={mainFieldOptions}
value={row.mainField}
onChange={(e, { value }) => {
setFieldValue(`rows.${index}.mainField`, value)
updateDropDownOptions(value)
}
}
/>
My options:
let mainField = [
{ key: "org", text: "org", value: "org" },
{ key: "role", text: "role", value: "role" },
{ key: "emailId", text: "emailId", value: "emailId" },
]
Also, I have:
const [mainFieldOptions, setMainFieldOptions] = useState(mainField)
And,
const updateDropDownOptions = (value:any) => {
let updatedOptions: { key: string; text: string; value: string }[] = []
mainFieldOptions.forEach(option => {
if(option.key != value){
updatedOptions.push({ key:option.key , text:option.key, value:option.key })
}
})
setMainFieldOptions(updatedOptions)
console.log("mainfield", mainField)
}
In onChange, if I dont call updateDropDownOptions() method, the dropdown value is set. But when I call the method, its giving blank value. Please help.
There are few changes required in your code,
You are pushing the entire initialValues when you are adding a row which is an [{}] but you need to push only {} so change your code to initialValues[0] in your push method.
Its not needed to maintain a additional state for the options. You can filter the options based on the selected option in other rows which is available in the values.rows .
Util for filtering the options
const getMainFieldOptions = (rows, index) => {
const selectedOptions = rows.filter((row, rowIndex) => rowIndex !== index);
const filteredOptions = mainField.filter(mainFieldOption => !selectedOptions.find(selectedOption => mainFieldOption.value === selectedOption.mainField));
return filteredOptions;
}
Call this util when rendering each row
values.rows.length > 0 &&
values.rows.map((row, index) => {
const mainFieldOptions = getMainFieldOptions(values.rows, index);
Working Sandbox

Fluent UI dropdown selection

I am using fluent ui dropdown So, when i click on dropdown the first option is selected by default without selecting. If i click on dropdown and without selecting if i click somewhere on the page nothing should be selected and dropdown should be closed is what i want. Please help me with this.
Below is the code which iam using:
<Dropdown
disabled={this.state.HideLocationFilter}
selectedKey={selectedItem1 ? selectedItem1.key : undefined}
onChange={this._onChange1}
placeholder="Location"
options={[
{ key: 'UK', text: 'UK' },
{ key: 'Hyderabad', text: 'Hyderabad' },
{ key: 'Bangalore', text: 'Bangalore' },
{ key: 'Ahmedabad', text: 'Ahmedabad' },
{ key: 'Pune', text: 'Pune' },
{ key: 'Mumbai', text: 'Mumbai' },
{ key: 'USA', text: 'USA' }
]}
styles={dropdownStyles}
/>
private _onChange1 = (event: React.FormEvent<HTMLDivElement>, item: IDropdownOption): void => {
console.log(`Selection change: ${item.text} ${item.selected ? 'selected' : 'unselected'}`);
if(item.selected == true)
{
this.setState({ selectedItem1: item });
}
};
Initially it is like this:location dropdown
When i click on dropdown it is selecting by default(find picture of the same):dropdown when clicked
There is an issue currently that when you press on the default option and click outside of the browser window and then click back in the dropdown selects the first option.
The solution for this is to looked at the type of event and short circuit the onChange if the type === 'focus':
const onChange = useCallback((e, value) => {
if (e.type === 'focus') return
// do if not type 'focus'
}, [])
In my situation I needed also to trigger a re-render and augmented the above as such:
const onChange = useCallback((callback) => (e, value) => {
if (e.type === 'focus') return callback()
// do if not type 'focus'
}, [])
Then when calling it:
<Dropdown
{...props}
onChange={onChange(() => setOnChangeTriggered(true))}
/>

Passing options with multiple values that are the same in to React-select

Below is a snippet of how I call the Select menu:
import Select from 'react-select';
const SelectMenu = (props) => {
const { options, defaultValue, onChange, name, label } = props;
return (
<Select
options={options}
defaultValue={defaultValue}
onChange={onChange}
name={name}
id="search-select"
/>
);
};
My options object looks as follows:
TestObj: [
{
label: 'Test 1',
value: 3.49
},
{
label: 'Test 2',
value: 3.99
},
{
label: 'Test 3',
value: 3.89
},
{
label: 'Test 4',
value: 3.99
},
{
label: 'Test 5',
value: 0
}
]
Because of the fact two of the options share the same value I get this effect:
As can be seen due to Test 2 and 4 sharing the same value they both show as selected.
Is there a way that whilst keeping the same values I can have this so that it only selects the actual selected option?
The way react-select by default checks if a value is selected is by extracting the value piece and comparing it to whats selected (an array of objects with a value key.)
Using isOptionSelected you can override the strategy employed:
isOptionSelected={(option, selectValue) => selectValue.some(i => i === option)}
This is more or less how the internal function works without extracting the value part, and instead compares the whole object.

React Selecting dropdown option multiple times

I am wanting to be able to select a language I have called "Skip" more than one time without it disappearing from the dropdown. Currently, any language I select will disappear from the dropdown. Is this possible? Here's my code:
const languages = [
{ key: 'skip', text: 'Skip', value: 'Skip' },
{ key: 'english', text: 'English', value: 'English' },
{ key: 'french', text: 'French', value: 'French' },
]
handleAudioInputChange = (e, { name, value }) => this.setState( { [name]: value })
<Form.Select fluid search label='Audio Channels' name='audiochannelsValue' value={audiochannelsValue} options={languages} placeholder='Choose Audio Channels' onChange={this.handleAudioInputChange} multiple = "true"/>
I've tried multiple things such as hideSelectedOptions = {false}, but this does not seem to work. Any ideas?
If you only want a string based on the user input, you could do:
handleAudioInputChange = (e, { value }) => {
this.setState(prevState => {
const newVal = `${prevState.audiochannelsValue.length ? '/' : ''}${value}`;
return {
textValue: prevState.textValue.concat(newVal),
audiochannelsValue: value
};
});
}
This will build a string based on the previous state and separate each value with /. Haven't tested it, but it should generate (in order):
English
English/Skip
English/Skip/French
English/Skip/French/Skip

Resources