Keep Semantic-React multiple selection dropdown open when removing an item - reactjs

I'm trying to keep the multiple selection dropdown available from Semanti-UI-React open when I remove a selected item, but still have it close onBlur. I can set closeOnBlur to false and I get the behavior I want for removing the selected item, but then I have to click the arrow on the dropdown to close it. I tried plugging into the relevant events to see if there was a way I could achieve the desired behavior manually, but oddly enough if closeOnBlur is set to false, I don't even get the onBlur event.
Here is my code:
<Dropdown
style={{whiteSpace: 'nowrap', zIndex: 9999}}
upward={true}
search={true}
multiple={true}
selection={true}
loading={loading}
options={options}
value={this.props.selectedCodes || []}
closeOnBlur={false}
onBlur={() => console.log('onBlur')}
onChange={ (e, d) => {
console.log('onChange');
const value = d.value as string[];
const displayValues = value.map(code => {
const lookupEntry = options.find(i => i.value === code);
return lookupEntry ? lookupEntry.text : '';
});
const displayValue = displayValues.reduce(
(result, text) => {
return `${result}, ${text}`;
}, ''\
);
this.props.onSelectionChange(value, displayValue);
}}
onClose={() => console.log('onClose')}
/>
Any suggestions on how to achieve my desired behavior, or insight as to why the onBlur even doesn't fire if closeOnBlur is set to false?

This component supports manual control over it's open/closed status view an open prop. So simply manage that prop via the state of your custom containing class;
getInitialState() {
return { open: false };
},
handleClose() {
this.setState({open: false});
},
handleOpen() {
this.setState({open: true});
}
render() {
return <Dropdown
open={this.state.open}
onBlur={this.handleClose}
onFocus={this.handleOpen}
...
/>;

Related

How to write a ternary that hides a component when 'Enter' is pressed?

I am working on an app that allows for doctors to fill out intake forms during surgeries or other procedures. On another page of the app, the doctor's are able to select favorite codes from the whole code list. A component of the intake form is an input field that searches a dataset for the doctor's favorite CPT and ICD-10 Codes. The issue is that the doctors do not want to go into the favorite code component to set a favorite while they are filling out the intake form. I created another input component that searches the favorite code list + the global code list. My goal is to create a ternary that shows the favorite code component, but when enter is pressed (assuming the code they are looking for is not in the favorites list) it will hide and the component that shows the favorites + the global list will be visible.
I am new to React so apologizes for the long winded question. Please let me know if you need to see any other code.
{show === true ?
<SearchableDropdown
label="ICD-10 Code"
showAddButton={false}
showCaretIcon={true}
options={fdata?.icdCodes?.map((code) => ({
content: `${code.code}` + ' - ' + `${code.description}`,
key: code.id,
value: code.id,
}))}
displayValue={state.icdCodes}
value={searchIcd}
onChange={(newValue) => {
setState({
...state,
icdCodes: newValue
});
}}
onKeyUp = {() => setShow(false)}
onSelectOption={(option) => {
setState({
...state,
icdCodes: option?.content,
});
}}
/>
:
<Search
label='ICD-10 Code'
placeholderText={valid ? (
"Search for an ICD-10 code"
) : (
searchText
)}
searchText={searchIcd}
setSearchText={setSearchIcd}
searchFunction={icdAction}
/>
Favorite List
Global List
EDIT: I get an error that states, "(e: any, show: any) => void' is not assignable to type '(newDisplayValue: string) => any'"
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
setShowFavorites(false);
console.log('keydown')
}
}
{showFavorites === true ?
<SearchableDropdown
label="ICD-10 Code"
showAddButton={false}
showCaretIcon={true}
options={fdata?.icdCodes?.map((code) => ({
content: `${code.code} - ${code.description}`,
key: code.id,
value: code.id,
}))}
displayValue={state.icdCodes}
value={searchIcd}
onChange={(newValue) => {
setState({
...state,
icdCodes: newValue
});
}}
onKeyDown={handleKeyDown}
onSelectOption={(option) => {
setState({
...state,
icdCodes: option?.content,
});
}}
/>
:
<Search
label='ICD-10 Code'
placeholderText={valid ? (
"Search for an ICD-10 code"
) : (
searchText
)}
searchText={searchIcd}
setSearchText={setSearchIcd}
searchFunction={icdAction}
/>
}
Your show variable should be a state for conditional rendering (consider more descriptive name):
const [showFavorites, setShowFavorites] = useState(true);
then your keyboard handler should look like this:
const handleKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
event.stopPropagation();
setShowFavorites(false);
}
}
and respectively in <SearchableDropdown>:
onKeyDown={handleKeyDown}
To modify your state when the user press enter in your input, you can do:
const handleKeyUp = (e) => {
if(e.key !== "Enter") return;
//do your logic here
};
//...
<input ... onKeyUp={handleKeyUp} />

#material-ui Autocomplete: set input value programmatically

I have an asynchronous Autocomplete component that works fine so far.
Hopefully the simplified code is understandable enough:
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
const [open, setOpen] = useState(false);
const [options, setOptions] = useState<T[]>();
const onSearch = (search: string) => {
fetchOptions(search).then(setOptions);
};
return (
<Autocomplete<T>
open={open}
onOpen={() => {
setOpen(true);
}}
onClose={() => {
setOpen(false);
}}
onChange={(event, value) => {
props.onChange(value as T);
}}
getOptionSelected={props.getOptionSelected}
getOptionLabel={props.getOptionLabel}
options={options}
value={(props.value as NonNullable<T>) || undefined}
renderInput={(params) => (
<TextField
{...params}
onChange={(event) => onSearch(event.currentTarget.value)}
/>
)}
/>
);
}
The component above works easily: when the user clicks on the input, the Autocomplete component displays an empty input field where the user can type in a value to search for. After the input has changed, the options are refetched to show matching results.
Now I want to add support for shortcodes: when the user types qq, the search term should be replaced by something, just like if the user would have typed something himself.
However, I found no way to update the value of the rendered TextField programmatically. Even if I set value directly on the TextField, it won't show my value but only the users input.
So, any ideas how to solve this problem?
Thank you very much.
What I've tried so far was to simply update the input within onKeyUp:
// ...
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
const value = event.currentTarget.value;
if(value === 'qq') {
event.currentTarget.value = 'something';
}
},
}}
/>
)}
With the code above I can see the something for a short time, but it gets replaced by the initial user input very soon.
Autocomplete is useful for setting the value of a single-line textbox in one of two types of scenarios: combobox and free solo.
combobox - The value for the textbox must be chosen from a predefined set.
You are using it so it not allowing you to add free text (onblur it replaced)
Answer: To take control of get and set value programmatically.
you need a state variable.
Check here codesandbox code sample taken from official doc
Your code with my comment:-
export function AsyncAutocomplete<T>(props: AsyncAutocompleteProps<T>) {
... //code removed for brevity
//This is a state variable to get and set text value programmatically.
const [value, setValue] = React.useState({name: (props.value as NonNullable<T>) || undefined});
return (
<Autocomplete<T>
... //code removed for brevity
//set value
value={value}
//get value
onChange={(event, newValue) => setValue(newValue)}
renderInput={(params) => (
<TextInput
{...params}
label={props.label}
onChange={(event) => onSearchChange(event.currentTarget.value)}
InputProps={{
...params.InputProps,
onKeyUp: (event) => {
//get value
const value = event.currentTarget.value;
//if qq then set 'something'
if (value === "qq") {
setValue({ name: "something" });
}
//otherwise set user free input text
else {
setValue({ name: value });
}
},
}}
/>
)}
/>
);
}

Hide/Remove "Create New" menu in react-select

i am using creatable select where i want to hide "create new" menu option. here is my
CodeSandbox i tried following but no luck promptTextCreator={() => false}
thanks you and appreciate any help
// try this way
return (
<CreatableSelect
isClearable
onChange={this.handleChange}
onInputChange={this.handleInputChange}
options={colourOptions}
noOptionsMessage={() => null}
// isValidNewOption={() => true}
// or `isValidNewOption={() => false}`
promptTextCreator={() => false}
/>
);
If you want to hide the create new value message at all times while still being able to create new values, you have to use the prop formatCreateLabel as follows formatCreateLabel={() => undefined} when you define your CreatableSelect.
Disabling create label via formatCreateLabel={() => undefined} is the right direction but the menu list sill shows empty space instead of not showing at all which is what you may prefer.
You may want to hide the menu list completely when there is no option by setting the menu list display to none
// Remember to define a unique id for your component in the constructor
// so you can target the right menu list element to hide it
id = "";
constructor(props) {
super(props);
this.id = "react-select_" + Math.random().toFixed(8).slice(2);
}
handleInputChange = (inputValue: any, actionMeta: any) => {
setTimeout(() => {
const menuEl = document.querySelector(`#${this.id} [class*="-menu"]`);
const menuListEl = document.querySelector(
`#${this.id} [class*="MenuList"]`
);
if (
menuListEl.children.length === 1 &&
menuListEl.children[0].innerHTML === ""
) {
menuEl.style.display = "none";
} else {
menuEl.style.display = "block";
}
});
};
...
<CreatableSelect
id={this.id}
onInputChange={this.handleInputChange}
formatCreateLabel={() => undefined}
...
/>
Live Demo
Just add these props:
menuIsOpen={false} and components={{ DropdownIndicator: null }}
Then handle onKeyDown and onInputChange event as explained in => https://react-select.com/creatable, have a look into "Multi-select text input" section
Here is the complete example:
import React, { Component } from 'react';
import CreatableSelect from 'react-select/creatable';
const components = {
DropdownIndicator: null,
};
const createOption = (label: string) => ({
label,
value: label,
});
export default class CreatableInputOnly extends Component<*, State> {
state = {
inputValue: '',
value: [],
};
handleChange = (value: any, actionMeta: any) => {
this.setState({ value });
};
handleInputChange = (inputValue: string) => {
this.setState({ inputValue });
};
handleKeyDown = (event: SyntheticKeyboardEvent<HTMLElement>) => {
const { inputValue, value } = this.state;
if (!inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
this.setState({
inputValue: '',
value: [...value, createOption(inputValue)],
});
event.preventDefault();
}
};
render() {
const { inputValue, value } = this.state;
return (
<CreatableSelect
components={components}
inputValue={inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={this.handleChange}
onInputChange={this.handleInputChange}
onKeyDown={this.handleKeyDown}
placeholder="Type something and press enter..."
value={value}
/>
);
}
}
Just add
<CreatableSelect
components={{
...components,
Menu: () => null
}}
/>
Just treat all the new options as invalid, it will show "No options" message:
<CreatableSelect isValidNewOption={() => false}/>
You may want to show "Create" option to user but if element exists or any other reason the "Create" option should be hidden.
For exp:
options=[
{label:"A - 1",value:"A"},
{label:"B - 1",value:"B"}
{label:"C - 1",value:"C"}
{label:"D - 1",value:"D"}
]
In my case, user can only Create A, B, C, D but i have formatted their input to make the label look as "A - 1" , "B - 1" and so on. Now if user again enters A, or B or C or D, it will not match with "A - 1", or "B - 1" or "C - 1" or "D - 1" respectively, in this case i want to hide "Create" option because I am already accepting that value but with different format.
So my logic should go as
<CreatableSelect
name="options"
options={options}
placeholder="Select or Create"
isSearchable
onChange={(option) => appendLabel(option.value)}
value={null}
isValidNewOption={(inputValue)=>
(options.filter((lab)=>lab?.label?.split(" — ")
[0].trim().toLowerCase()===inputValue.toLowerCase()).length>0?false:`Create ${inputValue}`}
/>

prevent a button being clicked consecutively in React

I am trying to prevent a button being clicked consecutively in my project and only allow a single click. I would also like it to only be a single click and not allow a double click if that is possible?
To do this I would like to add a time out of maybe 5 seconds before the button can be pressed again but I'm not sure how to do this. The button is a link to redirect the user back to the homepage.
Is the a way to set the button on a timer when clicked?
<Button id="back-btn" variant="link" className="btn btn-link" onClick={props.goBack} alt="homepage">
Homepage
</Button>
Any ideas?
Cheers
R
basically you need to use a disabled state with a timer.
check this codepen: https://codepen.io/hasanagh/pen/MWaLxVK
state = {
disabled: false,
};
handleButtonClicked = () => {
//going back logic
this.setState({
disabled: true,
});
setTimeout(() => {
this.setState(() => ({
disabled: false,
}));
}, 5000);
};
render() {
const { disabled } = this.state;
return (
<button
onClick={this.handleButtonClicked}
disabled={disabled}
>
Button to be disabled
</button>
);
}
Also, not sure why you need it to be 5 sec if this is related to a certain event better bind to event than time.
It's probably most re-useable to make your button component. You could handle the onClick event to set a disabled state, then start a timer to set it back to false. Example:
const DebouncedButton = ({ as = button, delay, onClick, ...props }) => {
const [isDisabled, setDisabled] = useState(false);
useEffect(() => {
if (!isDisabled) {
// timeout elapsed, nothing to do
return;
}
// isDisabled was changed to true, set back to false after `delay`
const handle = setTimeout(() => {
setDisabled(false);
}, delay);
return () => clearTimeout(handle);
}, [isDisabled, delay]);
const handleClick = (e) => {
if (isDisabled) {
return;
}
setDisabled(true);
return onClick(e);
};
const Component = as;
return <Component {...props} disabled={isDisabled} onClick={handleClick} />;
};
You would use this component just like you'd use a button, except that you pass it a delay which is the amount of time in milliseconds it should be disabled after clicking. The as prop lets you pass the component which is used for the button itself, defaulting to <button>.
<DebouncedButton
as={Button}
delay={5000}
id="back-btn"
variant="link"
className="btn btn-link"
onClick={() => console.log('click!')}
alt="homepage"
/>
Currently it sets the disabled property of the button to true, but if you don't want the visual, just remove disabled={isDisabled} from the component.

Selected Value not showing in Textfield Select - Material UI React Component

I have TextField Select Material UI components based on a certain number of value in a variable.
{this.state.selectedNextHops.map((nextHop, index) => (
<div>
<TextField
select
className="vnfprofile-field"
InputProps={{ className: 'variable-value site-details-view-textfield' }}
InputLabelProps={{ shrink: true }}
SelectProps={{
MenuProps: {
className: 'vnf-designer-value',
getContentAnchorEl: null,
anchorOrigin: {
vertical: 'bottom',
horizontal: 'left',
}
},
}}
value = {this.state.selectedNextHops[index] || ''}
disabled={this.props.newPopoverPanelView === 'VIEW' ? true : false}
onChange={(e) => this.handleChange('nexthop', e)}
>
{this.state.remainingNextHops.length !== 0 ? this.state.remainingNextHops.map((option, i) => (
<MenuItem key ={i} value = {option || ''}>
{option}
</MenuItem>
)) :
<MenuItem value = {'No Data Available'}>
{'No Data Available'}
</MenuItem>}
</TextField>
<TextField
className="vnfprofile-field subnet-textfield"
InputProps={{ className: 'variable-value' }}
InputLabelProps={{ shrink: true }}
value = {'29'}
/>
</div>
))
}
The TextFields show up sequentially when I select value from the previous dropdown and filters the menu based on previous selection.
if(selectedNextHops.indexOf(event.target.value) === -1) {
selectedNextHops.push(event.target.value);
}
remainingNextHops = this.props.nextHopSapds.filter(nextHop => selectedNextHops.indexOf(nextHop) === -1);
this.setState({
selectedNextHops: selectedNextHops,
remainingNextHops: remainingNextHops
});
Update: Here is my handleChange Method ->
handleChange(type, event) {
let selectedNextHops = JSON.parse(JSON.stringify(this.state.selectedNextHops));
let remainingNextHops = [];
if(type === 'nexthop') {
selectedNextHops = selectedNextHops.filter(nh => nh !== '');
isContentChanged = true;
if(selectedNextHops.indexOf(event.target.value) === -1) {
selectedNextHops.push(event.target.value);
}
remainingNextHops = this.props.nextHopSapds.filter(nextHop => selectedNextHops.indexOf(nextHop) === -1);
if(remainingNextHops.length !== 0) {
selectedNextHops.push('');
}
this.setState({
selectedNextHops: selectedNextHops,
remainingNextHops: remainingNextHops
});
}
}
The state is updating fine, but the textfield does not display the selected value. I have tried everything I knew. Any help is appreciated.
This is hard to debug without seeing a working snippet or the state ( especially this.state.selectedNextHops) , but based on the code sandbox provided ( in the comment ) , I assume it's the same problem, so this answer will apply to the sandbox code :
this.setState({
selectedID: event.target.value.id,
visibleValue: event.target.value.name
});
event.target.value.id and event.target.value.name are undefined,
console.log(console.log(event.target)) // {value: "S0002", name: undefined}
For the select to display a selected option, the value attribute for both need to match :
<select value="2">
^^^^^^^^^
<option value="1">first value</option>
<option value="2">second value</option>
^^^^^^^^^
</select>
in the example in the code sandbox, the value of the Select is value={this.state.visibleValue} and the values of the options are value={x.label}
Since this.state.visibleValue is always undefined, you'll never see the value of the select update.
A quick fix for this is to change the handleChage function to :
handleChangeTest = event => {
this.setState({
selectedID: event.target.id,
visibleValue: event.target.value
});
};
but that will leave selectedID undefined , to set it, add the attribute id={x.id} to the option and use event.currentTarget to get its value :
{this.state.data.map(x => (
<MenuItem key={x.id} value={x.label} id={x.id}>
^^^^^^^^^
{x.name}
</MenuItem>
))}
And
handleChangeTest = event => {
this.setState({
selectedID: event.currentTarget.id,
^^^^^^^^^^^^^^^^^^^^^^
visibleValue: event.target.value
});
};
Working SandBox
So you try to access the key with e.target.value.id but the target object has only the value and not the id itself. That is why it is undefined after you call the handleChange method. There is a way to access the key though:
The callback does not only pass the event but also the child object as second parameter and this can be used to get the key like this:
handleChangeTest = (event, child) => {
this.setState({
selectedID: child.key,
visibleValue: event.target.value
});
};
This will set the key as selectedID and the value of the selected item as visibleValue.

Resources