react-select Creatable: override default behavior upon pressing Enter key - react-select

I'm using react-select Creatable for my project, and I need to override its default behavior. Currently whenever the user types something in and presses Enter key, it gets added as a new option.
Is there a way to change it to "whenever the user types something in and presses Space bar, it gets added as a new option"?

If you just do
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
}}
...in your component, that'll stop the Enter key from doing anything.
To get the space bar to create a new entry, I think you might want to make a KeyDown handler:
function handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
if (e.key === "Space") {
handleChange();
}
// in component:
onKeyDown={handleKeyDown}
But I have not tried it yet.
Edit: Solution to keep Enter key from submitting a form the CreatableSelect is embedded within, while still allowing Enter to do react-select's default "create a new entry" behavior:
export function TagEditor({data, handleSave}) {
const [newValues, setNewValues] = useState(...our data mgr);
const [lastKey, setLastKey] = useState('');
const handleChange = (newValue) => {
setNewValues(newValue);
};
function handleKeyDown(e) {
/* React Select hack for pages where tags are inside a form element:
Default CreatableSelect functionality is that Enter key creates
the new tag. So we let that happen, but if they hit Enter again,
we kill it so the form doesn't submit. Yes, it's hack-y.
*/
switch (e.key) {
case 'Enter':
if (lastKey === 'Enter') {
e.preventDefault();
e.stopPropagation();
}
setLastKey('Enter');
break;
default:
setLastKey('');
}
}
return <CreatableSelect
aria-label="Add Tags"
autoFocus={true}
blurInputOnSelect={false}
defaultValue={...our data manager function}
inputId="tagmanager"
isClearable={false}
isMulti
name="my_field_name"
onBlur={(e) => {
e.preventDefault();
e.stopPropagation();
handleSave(newValues);
}}
onChange={handleChange}
onKeyDown={handleKeyDown}
openMenuOnFocus={true}
options={data}
placeholder="Select or type new..."
searchable
styles={our styles...}
/>;
}
Note this hack is needed because we're using stateless functional components, and react-select is still a state-full class component.

Related

How to reset a FluentUI Dropdown selection on a change somewhere else?

I'm using a FluentUI dropdown in a reactjs office addin and would like for it to be reset when the user types in a completely different textbox. Can I get a reference to the dropdown and call some reset function?
It's a plain single-select dropdown like:
return <Dropdown
placeholder={"Pick a thing"}
label={"Things"}
options={ thingOptions }
onChange={ handleSelectThing }
>
I saw https://github.com/microsoft/fluentui/issues/5917 but it doesn't quite seem to be the same thing.
I'm not thinking reactively enough. The solution was quite simple when it came down to it:
this.state = {
// other state,
selectedThing: undefined
}
const selectedThingKey =
thingOptions.reduce(
(acc, elt, index) => {
return acc || (this.state.selectedThing === index && elt.key)
},
false);
return <Dropdown
options={ thingOptions }
onChange={ (ev,op,i) => {
this.setState({selectedThing: i});
return handleSelectThing(ev, op, i);
}
}
selectedKey={ selectedThingKey }
notifyOnReselect={true}
/>

How to toggle a boolean value using Context and Hooks?

I am using ReactContext and Hooks to show and hide a Modal on click of a button.
Following is my Context code
const setPrivacyPolicyModalVisibility = dispatch => {
return ({visible}) => {
visible
? dispatch({type: 'enablePrivacyPolicyModalVisibility'})
: dispatch({type: 'disablePrivacyPolicyModalVisibility'});
};
};
And the reducer code for the same is something as follows
case 'enablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: true}};
case 'disablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: false}};
Some setup code in my class
const {state, setPrivacyPolicyModalVisibility} = useContext(Context);
const [privacyVisibility, setPrivacyVisibility] = useState(false);
on click of button I am calling the following code
<TouchableOpacity
onPress={() => {
setPrivacyVisibility(true);
console.log(`${privacyVisibility}`);
setPrivacyPolicyModalVisibility({privacyVisibility});
}}.....
As you can see I am console logging the privacyVisibility value but it is always false which I fail to understand
Following is my code in the component to hide or show the Modal
{state.enablePrivacyPolicy ? (
<SettingsPrivacyModal visible={true} />
) : (
<SettingsPrivacyModal visible={false} />
)}
The Modal code is proper as I have tried setting default value to true just to check if modal is visible then it works, but on click of button press the state value does not change and I am not able to see the modal as the value is always false
The issue seems to be in the onPress callback:
onPress={() => {
const privacyVisibility_new = !privacyVisibility;
console.log( privacyVisibility_new );
setPrivacyVisibility( privacyVisibility_new );
setPrivacyPolicyModalVisibility( privacyVisibility:privacyVisibility_new );
}}
When the cycle reaches the callback privacyVisibility has the default which is false. I think you are assuming that once setPrivacyVisibility is called, the privacyVisibility variable will have the new value in that same cycle; but it won't have the updated value until the component renders again.
setPrivacyPolicyModalVisibility doesn't seem to be correct. I am not sure where is dispatch exactly, but assuming it is at the same level as the function you can simply use it inside.
const setPrivacyPolicyModalVisibility = visible => {
if ( visible ) {
dispatch({ type: "enablePrivacyPolicyModalVisibility" });
} else {
dispatch({ type: "disablePrivacyPolicyModalVisibility" });
}
};
You might want to simplify your reducer and send directly the visible value:
const setPrivacyPolicyModalVisibility = visible =>
dispatch({ type: "setPrivacyPolicyModalVisibility", payload: visible });
.
case 'setPrivacyPolicyModalVisibility':
return { ...state, is_privacyPolicy_visible: action.payload };
Actually the error was simple. I am using the visible parameter as props in setPrivacyPolicyModalVisibility but while setting I am passing prop of different name
Thanks to #Alvaro for pointing me in the right direction

How to trigger change event on slate.js when testing with Selenium or Cypress

I'm trying to find a way to simulate a "change" event when doing E2E testing (with selenium or cypress) and slate.js
In our UI, when the user clicks on a word, we pop-up a modal (related to that word). I've been unable to make this happen as I can't get the change event to trigger
The Cypress input commands (e.g. cy.type() and cy.clear()) work by dispatching input and change events - in the case of cy.type(), one per character. This mimics the behavior of a real browser as a user types on their keyboard and is enough to trigger the behavior of most application JavaScript.
However, Slate relies almost exclusively on the beforeinput event (see here https://docs.slatejs.org/concepts/xx-migrating#beforeinput) which is a new browser technology and an event which the Cypress input commands don’t simulate. Hopefully the Cypress team will update their input commands to dispatch the beforeinput event, but until they do I’ve created a couple of simple custom commands which will trigger Slate’s input event listeners and make it respond.
// commands.js
Cypress.Commands.add('getEditor', (selector) => {
return cy.get(selector)
.click();
});
Cypress.Commands.add('typeInSlate', { prevSubject: true }, (subject, text) => {
return cy.wrap(subject)
.then(subject => {
subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'insertText', data: text }));
return subject;
})
});
Cypress.Commands.add('clearInSlate', { prevSubject: true }, (subject) => {
return cy.wrap(subject)
.then(subject => {
subject[0].dispatchEvent(new InputEvent('beforeinput', { inputType: 'deleteHardLineBackward' }))
return subject;
})
});
// slateEditor.spec.js
cy.getEditor('[data-testid=slateEditor1] [contenteditable]')
.typeInSlate('Some input text ');
cy.getEditor('[data-testid=slateEditor2] [contenteditable]')
.clearInSlate()
.typeInSlate('http://httpbin.org/status/409');
If you need to support other inputTypes, all of the inputTypes supported by Slate are listed in the source code for editable.tsx
Found a solution:
1) Add a ref to the Editor
<Editor
ref={this.editor}
/>
2) Add a document listener for a custom event
componentDidMount() {
document.addEventListener("Test_SelectWord", this.onTestSelectWord)
}
componentWillUnmount() {
document.removeEventListener("Test_SelectWord", this.onTestSelectWord)
}
3) Create a handler that creates a custom select event
onTestSelectWord(val: any) {
let slateEditor = val.detail.parentElement.parentElement.parentElement.parentElement
// Events are special, can't use spread or Object.keys
let selectEvent: any = {}
for (let key in val) {
if (key === 'currentTarget') {
selectEvent['currentTarget'] = slateEditor
}
else if (key === 'type') {
selectEvent['type'] = "select"
}
else {
selectEvent[key] = val[key]
}
}
// Make selection
let selection = window.getSelection();
let range = document.createRange();
range.selectNodeContents(val.detail);
selection.removeAllRanges();
selection.addRange(range)
// Fire select event
this.editor.current.onEvent("onSelect", selectEvent)
}
4) User the following in your test code:
arr = Array.from(document.querySelectorAll(".cl-token-node"))
text = arr.filter(element => element.children[0].innerText === "*WORD_YOU_ARE_SELECTING*")[0].children[0].children[0]
var event = new CustomEvent("Test_SelectWord", {detail: text})
document.dispatchEvent(event, text)
Cypress can explicitly trigger events: https://docs.cypress.io/api/commands/trigger.html#Syntax
This may work for you:
cy.get(#element).trigger("change")

Aurelia `click` attribute that requires event target to be same as element

I'm aware of click.trigger as well as click.delegate which work fine. But what if I want to assign a click event that should only trigger when the exact element that has the attribute gets clicked?
I'd probably do something like this were it "normal" JS:
el.addEventListener('click', function (e) {
if (e.target === el) {
// continue...
}
else {
// el wasn't clicked directly
}
});
Is there already such an attribute, or do I need to create one myself? And if so, I'd like it to be similar to the others, something like click.target="someMethod()". How can I accomplish this?
Edit: I've tried this which doesn't work because the callback function's this points to the custom attribute class - not the element using the attribute's class;
import { inject } from 'aurelia-framework';
#inject(Element)
export class ClickTargetCustomAttribute {
constructor (element) {
this.element = element;
this.handleClick = e => {
console.log('Handling click...');
if (e.target === this.element && typeof this.value === 'function') {
console.log('Target and el are same and value is function :D');
this.value(e);
}
else {
console.log('Target and el are NOT same :/');
}
};
}
attached () {
this.element.addEventListener('click', this.handleClick);
}
detached () {
this.element.removeEventListener('click', this.handleClick);
}
}
And I'm using it like this:
<div click-target.bind="toggleOpen">
....other stuff...
</div>
(Inside this template's viewModel the toggleOpen() method's this is ClickTargetCustomAttribute when invoked from the custom attribute...)
I'd also prefer if click-target.bind="functionName" could instead be click.target="functionName()" just like the native ones are.
Just use smth like click.delegate="toggleOpen($event)".
$event is triggered event, so you can handle it in toggleOpen
toggleOpen(event) {
// check event.target here
}
Also you can pass any other value available in template context to toggleOpen.

Data table on click on dynamic controls

I have a jquery data table that I am populating from a drop down on change event. I have two check boxes in the data table and I am running an onclick on the check boxes. But on the first click the jquery does not fire only when I click it a second time does the jquery fire, also happens on switching pages.I added the .on() for the click, because I researched and saw that dynamic controls would work that way. Is there something I'm missing also to get this click function to work on first click? Below is some of my code.
data table click on check box control no jquery click event on first click
data table click on check box control on second click
$('#my-table').on('click', function () {
var i = -1;
$("input[id*='secondary']:checkbox").on("click", function () {
if ($(this).is(':checked')) {
i = selectedIds.indexOf($(this).val());
if (i === -1) {
selectedIds.push($(this).val());
}
CheckedSecondary(this);
}
else {
jQuery(this).closest("tr").css("background-color", "");
if (selectedIds.length > 0) {
i = selectedIds.indexOf($(this).val());
if (i != -1) {
selectedIds.splice(i, 1);
}
}
if (!primaryChecked)
$(this).closest('tr').find('input[type="checkbox"]').not(this).attr('disabled', false);
}
});
$("#my-table").find("input[id*='primary']:checkbox").on("click", function () {
if ($(this).is(':checked')) {
primaryChecked = true;
primaryID = this.value;
CheckedPrimary(this);
}
else {
primaryID = "";
primaryChecked = false;
$(this).closest('tr').find('input[type="checkbox"]').not(this).attr('disabled', false);
$('input:checkbox[id^="primary"]').each(function () {
if (!$(this).closest('tr').find('input[type="checkbox"]').is(':checked'))
$(this).attr('disabled', false);
});
jQuery(this).closest("tr").css("background-color", "");
}
});
});
You're attaching click handler inside another click handler which doesn't make sense.
Remove first click handler:
$('#my-table').on('click', function () {
});
Attach the click handler to the checkboxes as follows:
$('#my-table').on('click', "input[id*='secondary']:checkbox", function () {
});
and
$('#my-table').on('click', "input[id*='primary']:checkbox", function () {
});