Adding labels to react-select breaks functionally - javascript

I have have created a redux-form that used react-select. My input field component allows me to choose a select option OR create a new option by typing into the input/select box. Example of this working well codesandbox - https://codesandbox.io/s/o49kjl09j9
The next part of my component is to group the options by the data label - you will see how this works via this codesandbox - https://codesandbox.io/s/k5pyr75xor
The issue is after I have applied my changes for the groups to work I can no longer type a new option in my select/input box. Not sure how to fix the error. I've changed how the data is structured coming in to create the groups but can't seem to implement the below code into my sandbox - https://codesandbox.io/s/k5pyr75xor.
UPDATE: The issue is that the created value does not have a label - but I would add a default label to created options. e.g. "Custom"
This is the code that the react-select docs recommend without success:
const formatGroupLabel = data => (
<div style={groupStyles}>
<span>{data.label}</span>
<span style={groupBadgeStyles}>{data.options.length}</span>
</div>
);
export default () => (
<Select
defaultValue={colourOptions[1]}
options={groupedOptions}
formatGroupLabel={formatGroupLabel}
/>
);
Any assistance would be appreciated here.
UPDATE: Know issue here and solution - https://github.com/JedWatson/react-select/pull/2659
I need help implementing the solution.

I managed to dive into the issue.
The solution was to write your own isValidNewOption to replace the builtin and pass it as a property to the creatable select because Using the options from http://jedwatson.github.io/react-select/ "Numeric Values" breaks it cause .toLowerCase() doesn't work on numbers
Fixed example: https://codesandbox.io/s/p72l42pz30
isValidNewOption = (inputValue, selectValue, selectOptions) => {
if (
inputValue.trim().length === 0 ||
selectOptions.find(option => option.name === inputValue)
) {
return false;
}
return true;
};
and adding this to the select:
isValidNewOption={this.isValidNewOption}

Related

MUI "Master Detail" disable functionality

Regarding the Master Detail feature of MUI; when exporting to CSV from a Data Grid while implementing Master Detail the CSV export functionality stops working (understandably). Technically it works but only exports the first row (in my case). I looked around for a disable functionality of the master detail like there is for disableRowGrouping prop for groups but was not able to find one. Does this functionality exist and if not, do you have any suggestions on how I can turn on and off the Master Detail prop?
I did try conditionally adding the master detail feature to the DataGridPro component using state and a ternary statement like {!!someState ? getDetailPanelContent={({ row }) => <div>Row ID: {row.id}</div>}: false} however was not able to do so. I'm not sure if you can have conditional component props. Is this when a spread operator is used? If so, maybe someone can give an example of how to implement these two together?
I believe this is what you might call a rookie mistake. I was applying a ternary operation to the entire property and not just the value of the property.
Incorrect: {!!someState ? getDetailPanelContent={({ row }) => <div>Row ID: {row.id}</div>}: false}
Correct: {getDetailPanelContent= {!!someState ? {({ row }) => <div>Row ID: {row.id}</div>}: false} : null}.
Here is a code snippet:
// Custom State to manage disabling of panelContent
const [disablePanelContent, setDisablePanelContent] = useState(true);
// queryResponse is data being put into the DataGrid; '[0]' being the first row returned
// columnToTrigger is the column from the queryResponse that I want to trigger either showing or disabling the panelContent
if (!!queryResponse[0].columnToTrigger {
setDisablePanelContent(false);
} else {
setDisablePanelContent(true);
}
< DataGridPro
// ...
getDetailPanelContent = {!!disablePanelContent ? null : getDetailPanelContent} // Answer
// ...
/>
The solution below didn't work for me to conditionally render the expand icons for the master detail, so I wanted to share my working solution:
// MUI: Always memoize the function provided to getDetailPanelContent and getDetailPanelHeight.
const getDetailPanelContent = useCallback((params: GridRowParams<any>) => <CustomDetailPanel params={params} rowExpansion={rowExpansion}/>, []);
// Inside of the datagrid - conditionally render based on rowExpansion
{...(_.isEmpty(rowExpansion) ? {} : { getDetailPanelContent:getDetailPanelContent })}

ReactJS autocomplete from React Bootstrap not working

I'm trying to build an autocomplete search field, using this form component from React Bootstrap, which is rendered as a input by the browser.
Here's what I did in my React component:
<FormControl
id="frenchToEnglishInput"
placeholder="type a word..."
aria-label="Recipient's username"
autocomplete="on"
data={frenchToEnglishWords}
onChange={this.fetchFrenchToEnglish}
renderItem = {item => {
return (
<div>
{item}
</div>
);
}}
/>
the frenchToEnglishWords array is declared outside the component as a var, as I intend to update it as I type some value into the input field.
Now here is the function that triggers the onChange event :
fetchFrenchToEnglish = async () => {
if(document.getElementById("frenchToEnglishInput").value!==''){
axios.get(dictionaryURIs.english.French_English+""+document.getElementById("frenchToEnglishInput").value)
.then(response => {
frenchToEnglishWords = response.data
})
}
}
The request is made from MongoDb, after setting up an autocomplete index on a collection, but this part works fine.
My question is, why is the only "autocomplete" I'm getting is the one made of the previous words I've typed ?
Or maybe the array I'm using as input data must be a const (as I've seen in many examples) and not a var ?
When I do type in some word, I do not get any autosuggestion from the frenchToEnglishWords, which is being updated from the DB.
You need to use State!
This is not working because the data field is not a State, you need to bind the fetchFrenchToEnglish function to the data state.
But first of all, there's no reason to use var, because the most differences between them is just scope and immutability, here is more about it.
Also, you can use hooks like useState and useRef to no use getElementById.

Is there a way to include group label while search options in "React Select"

I have used react-select to allow users search/filter from a list of option. However, group labels are not included in the search. Just wondering if there is any way to include group label in the search.
In below screenshot, the group label "COLORECTAL" and its options were not shown when searching "COLOR".
I completely agree with Nitsew.
You could try to start with a filterOption props like this:
const filterOption = ({ label, value }, string) => {
// default search
if (label.includes(string) || value.includes(string)) return true;
// check if a group as the filter string as label
const groupOptions = groupedOptions.filter(group =>
group.label.toLocaleLowerCase().includes(string)
);
if (groupOptions) {
for (const groupOption of groupOptions) {
// Check if current option is in group
const option = groupOption.options.find(opt => opt.value === value);
if (option) {
return true;
}
}
}
return false;
};
function App() {
return (
<div className="App">
<Select
defaultValue={colourOptions[1]}
filterOption={filterOption}
options={groupedOptions}
/>
</div>
);
}
Live Code SandBox example.
The react-select library does not support this out of the box. Your best solutions would be to either open an issue on the repo to request this functionality, or fork the repo and implement this functionality yourself.
Choosing the first option leaves you in a position of uncertainty– The feature request may never get approved and implemented or it could take several months.
The other solution leaves you in a situation where you take ownership of the codebase and any existing or future bugs that may come up.
You could also combine these two options and open a feature request on the repo and include the required code changes to support it. This might be nice as you would become a contributor to a widely used NPM package.
Either way, it looks like this is the line of code that combines the label and value that searches get queried against: https://github.com/JedWatson/react-select/blob/master/packages/react-select/src/filters.js#L14
I can't write the full implementation for you, but you would need to somehow include the group label in this string, and then figure out the UI to show the selection state for that group.
if (!string) return true;
You can add this line before comment default search to handle the default list of options in the first time render the react-select.

SweetAlert2: How to get back values from react component?

With sweetalert2 now there is sweetalert2-react-content, that lets you use React components to show inside Swal.
It's good, but I can't seem to find how to get back values from that component.
For example, I have a component, that has two inputs.
MySwal.fire({
html: <MyComponent />
}).then(value => {
// how to get here my values from MyComponent??
});
I want get whether checkboxes are checked or not from that component.
I tried to have the state for those inputs and onChangeHandler in the component where I call MySwal. But that didn't seem to work at all, the checkboxes would not change.
In the previous version of this library there was swal.setActionValue, that seemed be what I need, but it doesn't work with the current sweetlalert2 version.
To sum up, when I press OK on the prompt, I want to get the value in the promise, that would be set by the MyComponent.
DEMO
You can try using preConfirm() as per the documentation. From perConfirm we can pass custom values.
Please find this example : https://codesandbox.io/s/kk2nv9nmy7
The following trick should do the job:
let returnedValue;
MySwal.fire({
html: <Options onChange={value => (returnedValue = value)} />
}).then(value => {
console.log(returnedValue);
});
Long story short you could pass onChange callback to the component and use it for updating a variable which stores a value. It's not very pretty but it does the job.
You could find the full example here https://codesandbox.io/s/o1005xv1q

With react-select, select multiple items matching search simultaneously

I am using react-select to display a searchable drop-down list of items, of which the user can select multiple items. My list is quite long and the user will often wish to multi-select many items which match the same filter string, which is a bit of a tedious procedure because each time you select an item the dropdown disappears and you need to re-type the search.
For example, the following sandbox has a react-select which lists lots of apples and cheeses. In order to select all the Apples, one would have to keep typing "Apple" and choosing one apple at a time.
https://codesandbox.io/s/2l99lry5p
Coming from desktop UI background, I naturally want to be able to type a search query and press Ctrl-A to select all of the matching search results and add them to my list, or Ctrl-Click to cherry pick multiple items from the matching set. But as far as I can tell there's no support for any hotkey like this in react-select.
Does the react-select API have any way that I can implement a "select all" hotkey which would select everything that matches the current search filter (or even an explicit "select all matches" button on the page would be fine)? I cannot see any programmatic way to get access to the set of objects which match the filter. Is this something that I would need to fork react-select to implement or is it possible to do this via the existing API somehow?
React Select has built-in props that can be used to prevent the menu from closing on select and persist the search string.
First prevent the menu from closing on select by setting closeMenuOnSelect to false.
Then onInputChange store the search string in state if the action equals 'input-change'.
Setting inputValue to this.state.value will persist the search string in the input field.
class Foo extends Component {
constructor(props) {
super(props);
this.state = {
value: ''
};
}
handleInputChange = (value, e) => {
if (e.action === 'input-change') {
this.setState({value});
}
}
render() {
return (
<Select
isMulti
name="colors"
options={options.map(x => MakeOption(x))}
className="basic-multi-select"
classNamePrefix="select"
/* added these props */
closeMenuOnSelect={false}
onInputChange={this.handleInputChange}
inputValue={this.state.value}
/>
)
}
}
Updated sandbox: https://codesandbox.io/s/yvmzx6pn6z
I hacked up something that kind of does what I want but is quite ugly:
https://codesandbox.io/s/j7453qrmv
To use:
Try searching "apple", then press "Add all matching items to selection"
The approach:
As #wdm mentioned, there's a onInputChanged you can hook in to.
In onInputChanged, get the matching items store them in the state of the component
I add a button near the Select which allows the user to choose to copy the matching set of items into another state variable chosenItems
The react-select Select component has a value property that you can provide to programmatically choose the items. I pass state.chosenItems in to this.
This works but there were many things that make this a pain:
The onInputChanged handler gets called before the items matching the filter appear. I attempted to grab the matching items by DOM queries but it did not work because onInputChanged is too early. So rather than relying on react-select's filtering logic, I'm replicating the filtering logic in the onInputChanged handler. This is not great as there could be a discrepancy between my filtering logic and the displayed list of matching items.
Whenever you click after typing a search, the react-select clears the search, which invokes the onInputChanged event again. So by clicking on the custom "Add All Matching Items" button, it removes the filter, clearing the list, invoking onInputChanged and setting the state with a new list of matching items. To deal with this I needed to have a previousMatchingOptions state variable which keeps track of the matching items from the previous call to onInputChanged. This seems like a terrible hack.
Likewise, I attempted to hide/show my "Select All Matching Items" button based on whether there were currently more than one item that matches the search, but I was similarly thwarted by timing issues. I attempted to hack around this but kept getting caught up in corner cases.
The UI I came up with for "Select All Matching Items" doesn't feel integrated with the react-select very well. It would be nicer if it was part of their component rather than beside it.
By using the values property of the Select component, you are bypassing the component's internal management of its state, so the normal way of adding, removing, and clearing items does not work without reimplementing all that in a custom onChange handler which modifies the state.chosen which is passed to values. Managing this myself seems also less than desirable.
So, I have a solution, but if someone has something has a suggestion that is much cleaner and/or simpler I would be happy to hear it!
It seems like forking the control and doing these changes internal to the component might be the better way to go.
In my onInputChanged I attempted to get the matching search results directly from the DOM using some getElementsByClassName hackery, though this approach did not work because the onInputChanged
A very simple way of implementing a "Select All" option is overriding React-Select component with a custom component. For that you first need to import it as
import { default as ReactSelect } from 'react-select';
then create a custom component which defines a new Property named "allowSelectAll", and selects all the options when this property is set to 'true'.
const Select = props => {
if (props.allowSelectAll) {
if (props.value && (props.value.length === props.options.length)) {
return (
<ReactSelect
{...props}
value={[props.allOption]}
onChange={selected => props.onChange(selected.slice(1))}
/>
);
}
return (
<ReactSelect
{...props}
options={[props.allOption, ...props.options]}
onChange={selected => {
if (
selected.length > 0 &&
selected[selected.length - 1].value === props.allOption.value
) {
return props.onChange(props.options);
}
return props.onChange(selected);
}}
/>
);
}
return <ReactSelect {...props} />;
};
Select.propTypes = {
options: PropTypes.array,
value: PropTypes.any,
onChange: PropTypes.func,
allowSelectAll: PropTypes.bool,
allOption: PropTypes.shape({
label: PropTypes.string,
value: PropTypes.string
})
};
Select.defaultProps = {
allOption: {
label: "Select all",
value: "*"
}
};
Note: You can simply copy and paste the above given code and it will work absolutely fine.
And once that is done you can simply use the new 'Select' component with 'allowSelectAll' property set to true.
<Select allowSelectAll={true} isMulti={true} isSearchable={true} options={options} />
You can use the filterOption function like this:
<select
options={[{label: 'test', value: 1, customData: 'bla blub test'}]}
filterOption={(option, filter) => {
const { customData } = option.customData;
if(customData.toLowerCase().indexOf(filter.toLowerCase()) >= 0) {
return true;
}
}} />
Hope this will help you :)

Categories

Resources