How to prevent User from creating duplicate values using React-Select Creatable? - reactjs

I am using react-select' Creatable to make a dropdown and allow user to create new item to the list.
This is what I have:
<Creatable
name="form-field-name"
value={this.props.selectWorker}
options={this.props.selectWorkers}
onChange={this.props.handleSelectWorker}
/>
Right now user can create new name even though it already exist, creating duplicates like shown below.
I saw that there is an option called isOptionUnique on react-select site.
Searches for any matching option within the set of options. This
function prevents duplicate options from being created. By default
this is a basic, case-sensitive comparison of label and value.
Expected signature: ({ option: Object, options: Array, labelKey:
string, valueKey: string }): boolean
I have not been able to use it. I have tried isOptionUnique=true, isOptionUnique={options:this.props.workers}, but I got Creatable.js:173 Uncaught TypeError: isOptionUnique is not a function error.
I can't find an example for isOptionUnique, what is the best way to filter react-select to prevent duplicates using Creatable?

It's expecting a function
isOptionUnique(prop) {
const { option, options, valueKey, labelKey } = prop;
return !options.find(opt => option[valueKey] === opt[valueKey])
}
Don't forget to add it to your component instance
isOptionUnique={this.isOptionUnique}

This can also be achieved using the isValidNewOption prop.
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.email === inputValue)
) {
return false;
}
return true;
}
you define a function taking three parameter of the inputValue you typed in, the selectValue if a value is selected and the existing options as selectOptions.
The function should return true or false dependent on what conditions you want a new option to be valid. this will prevent addition of duplicates.
In the case above we prevent adding of new options if there is no text or if the email already exists in the available options

Related

How to use react-select-table's rows as options to react-select's input field

I have an input field that should accept multiple inputs from options that are already set. - This input field needs to appear like tag input fields so I used the React-select library:
The set options are going to come from a react-bootstrap-table so I disabled the built-in dropdown in the react-select input field.
The problem is that I cannot remove the selected items by deselecting the row from the react-bootstrap-table.
I think I am doing something wrong on how I'm passing the values between the input field and the table but can't figure it out.
Here is the codesandbox
The problem is you try to compare two Objects, however there is no generic means to determine that an object is equal to another.
You could JSON.stringify them both and compare but thats not reliable way.
Instead, you can filter the array by object key, lets say label.
In this case, your function should look like this:
const setSelected = (row: any, isSelect: any, rowIndex: any, e: any) => {
if (isSelect) {
setSelectProducts([...selectedProducts, row]);
} else {
const newArray = selectedProducts.filter(
(item) => item.label !== row.label
);
setSelectProducts(newArray);
}
};
Here is codesandbox to preview
#Edit
If you wanna do something onClear
onChange={(selectedOption, triggeredAction) => {
if (triggeredAction.action === 'clear') {
// Clear happened
}

React dynamic add select input field

I am trying Create a select field dynamically. When clicking the add button, it will dynamically add select fields. I have and issue where the values don't change.
My code can be viewed on codesandbox:
https://codesandbox.io/s/react-select-field-dynamically-uo4dy?file=/src/App.js
Take a look at my changes:
Forked sandbox
// I added a 3rd argument for the name:
const handleRoomChange = (option, index, name) => {
const value = option.value; // you had this as 'const { value } = option.value' which dereferences value twice
console.log(value);
const list = [...roomInputs];
list[index][name] = value; //you had this as list[index][value]
setRoomInputs(list);
};
// onChange passes the name as 3rd argument to handleRoomChange
<Select
name="boardBasic"
placeHolder="Board"
value={options.value}
onChange={option => handleRoomChange(option, i, "boardBasic")}
options={options}
styles={{
menu: (provided) => ({ ...provided, zIndex: 9999 })
}}
/>
You have three problems that I can see.
First, you only have a handleRoomChange function that is trying to handle both room changes and board changes. You should probably have a handleBoardChange function, as well.
Second, if you output option to the console inside handleRoomChange, you will notice that option.value is a number. So, when you proceed to set list[index][value] to value, what you're saying is that you want (for example) roomInputs[1][1] to be 1. roomInputs[1] has no 1 property, which is why you will end up with the wrong properties inside your object on submission. You need to be setting roomInputs[1].roomType to value, instead (and the boardBasic property in your handleBoardChange method, when you write it).
Third, you are trying to use object destructuring to assign object.value to your value variable...but you're trying to destructure object.value instead of destructuring object. For this reason, value is undefined every time you run the function. Replace the assignment with const { value } = option; and you will start getting defined values back.

how to avoid nested document's property assignment that results in undefined

This is how my incoming object from server looks like:
{
"name":"product1",
"categories": {
"cat1": {
"supported": false
},
"cat2": {
"supported": true
}
}
When the page loads, I will have 100s of categories and I populate categories object based on which category user selects. Those categories that were not selected by the user, don't exist in the product object.
When user is trying to edit the product, I have to show all the 100 categories in the checkboxes and show those categories checked which as supported set to true.
This is how my checkbox looks like:
data.props.categories.map((category, index) =>
<Form.Checkbox defaultChecked={productData.categories[category._id].supported} label={category.displayname}></Form.Checkbox>
);
This throws me an error saying when a category does not exist in product object because I am trying to access supported property of an undefined object. I am able to achieve what I need by writing a function that checks if a particular category exists in the incoming products object or not.
const isCategorySupported = (category_id) => {
debugger
if (productData.categories.hasOwnProperty(category_id)) {
return productData.categories[category_id].supported
}
return false
};
<Form.Checkbox defaultChecked={isCategorySupported(category._id)} label={category.displayname}></Form.Checkbox>
I was wondering if there is a better way or react way of doing this without writing a function?
Your solution looks fine. You may use optional chaining for this if you want a more elegant way:
<Form.Checkbox
defaultChecked={productData?.categories?.[category_id]?.supported}
label={category.displayname}>
</Form.Checkbox>
You have to keep in mind that this is not natively supported in the browsers just yet so a babel setup will be needed for this.
You are trying to map through categories object. So, you should be able to do like:
Object.keys(data.props.categories).map((category, index) =>
<Form.Checkbox defaultChecked={data.props.categories[category].supported} label={data.props.categories[category].displayname}></Form.Checkbox>
);

Accessing all form values from field validate function in React-Final-Form

I am creating wrapped versions of the React-Final-Form field component, essentially creating shareable types of fields to speed development at my company. As a part of that, I wanted to create a group of checkboxes, requiring at least one to be checked - but when I attempt to write the validate function, I only seem to have access to the current field.
Based on some other answers and the API documentation, I thought that the validate function received three parameters - validateMyField(value, allValues, fieldState).
When I passed my function to a given field, I get the following:
const mustPickAtLeastOne = (value, allValues, fieldState) => {
console.log(value) //output: false
console.log(allValues) //output: undefined
console.log(fieldState) //output: undefined
}
I was able to get the validation functional by simply adding a marker class to my checkbox fields, and then using a validation function like this:
const mustPickAtLeastOne = () => {
const numberChecked = document.querySelectorAll("." + markerclass + " input[type=\"checkbox\"]:checked").length;
return (numberChecked === 0 ? atLeastOneRequiredMsg : undefined);
}
This option is functional from a browser perspective, but smells pretty bad (both because I am attempting to interact with the underlying DOM nodes directly, and because I then can't test this via Jest/Enzyme - so I am missing unit test coverage to know if I break this in the future).
I am using React-Final-Form 6.3.0/Final-Form 4.16.1 - is there something wrong elsewhere, or why can't I get allValues in my validate function? Or is there an entirely better way to implement my "require at least one of these" validation?
Unfortunately, I had been starting off with the example at https://codesandbox.io/s/k10xq99zmr (making use of the composeValidators function) - as I wanted to allow consumers of my component to provide custom validation functions, as well as have the component itself have one or more built-in validations (and then take the combination of all of the above to pass to the underling field validate property)...which meant that the answer was staring me right in the face:
//...
const composeValidators = (...validators) => value =>
validators.reduce((error, validator) => error || validator(value), undefined);
///...
I corrected it to use the following:
//...
const composeValidators = (...validators) => (value, allValues, fieldState) =>
validators.reduce((error, validator) => error || validator(value, allValues, fieldState), undefined);
///...
Meaning that despite the underlying Final-Form implementation providing all three arguments to each assigned validator function, my use of the composeValidators sample was passing the current field value along...while throwing out/ignoring the other two.
Yes, using classes on the DOM nodes is gross. How about something like this?
const mustCheckAtLeastOne = (...fields) => (_, allValues, state) =>
fields.some(field => allValues[field]) ? undefined : "Must check one";
Is this not similar to what you want?
Alternatively, I just tried with this example with the form validate prop. https://codesandbox.io/s/react-final-form-simple-example-zu66s
It depends what kind of UI you are trying to do.
const validateForm = values => {
if (typeof values.sauces === "undefined" || values.sauces.length === 0)
return { sauces: "must choose one" };
};
// <Form validate={validateForm} ...

How can I disable checkbox for multi value selection in react-dropdown-tree-select?

I am new to React and I stumbled upon react-dropdown-tree-select for a specific need I had allowing users to view data in Tree format for selection in DropDown. But, I want the users to only select 1 value at a time. How can i enforce that?
There is no such property available directly in react-dropdown-tree-select. But you can listen to onChange event and reset the entire data which you have passed in data props with the updated data with only one node selected.
Check the following code.
onChange = (currentNode) => {
// keep reference of default data structure.
const updatedData = this.props.defaultData;
// find node related to currentData in your tree and set checked property
this.setState({data : updatedData });
}
render = () => {
return (
<DropdownTreeSelect
data={this.state.data}
onChange={this.onChange}
/>
);
}
This will basically stop the user from selecting multiple options instead of disabling remainig item you are unselecting the previously selected items.
If you just want one element selected at a time you can use mode="radioSelect"

Resources