I have a complex React component (Combobox from react-widgets), and would like to change a small part of it's behavior - I want to override the onKeyDown event. If enter is pressed, I want to handle it myself, and the Combobox shouldn't see the keypress. Apart from that, I'd like it to be a drop-in replacement.
I know React strongly recommends composition over inheritance, but in this case composing seems bad - if I make a new component that includes Combobox, I'd have to forward every prop to the combobox:
class MyCombobox extends Component {
render() {
return (<Combobox data={this.props.data}
value={this.props.value}
onChanged={this.props.onChanged}
...
/>);
}
}
I also tried just including a regular Combobox, and setting onKeyDown in its container, but that didn't override the event - the original Combobox still gets the event, and my Container gets it, too:
// in MyContainer.render:
return (
<div className="MyContainer">
<Combobox data={...} onKeyDown={this.handleKeyDown} />
</div>
);
This here almost works:
class MyCombobox extends Combobox {
handleKeyDown(event) {
console.log('MyCombobox.handleKeyDown', event);
console.log(this); // this: null!
if (event.key === 'Enter') {
event.preventDefault();
// super.close(); // fails
}
}
render() {
let result = super.render();
return React.cloneElement(result, {onKeyDown: this.handleKeyDown});
}
}
I can intercept the event and prevent the Combobox from processing it. However, this is null. If I make handleKeyPress an arrow function instead, I can access this (it is the MyCombobox object), but I can't access any of the Combobox's properties. Especially super.close() doesn't work (I get "'super' outside of function or class").
I'm not sure if it helps, but you can easily pass all the props to other component this way (using ES6 unwrapping):
class SomeComponent extends React.Component {
render() {
return(
<SomeOtherComponent {...this.props} />
)
}
}
I think it might help you to solve your problem using composition.
So, this kind of works, and it seems to be doing things The React Way (composition, only passing information to children via props).
What it does is, when I enter custom text and press enter, it takes that text and closes the menu, instead of discarding the text and selecting some element of the list.
It still doesn't behave 100% like a native combo box, so I'm giving up for now and trying a different component :-). But maybe this is a useful workaround for someone else trying to "subclass" a component.
You might be able to simplify this, I'm not sure the manual handling of value is neccessary.
class MyCombobox extends Component {
handleKeyDown = (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.setState({open: false, value: event.target.value});
}
}
constructor() {
super();
this.state = {open: false, value: null};
}
render() {
return (
<Combobox value={this.state.value} open={this.state.open}
onToggle={(isOpen)=>this.setState({open:isOpen})}
onChange={(newValue)=>this.setState({value:newValue})}
onKeyDown={this.handleKeyDown} {...this.props} />
);
}
}
Related
Have a look at the following example. I have enhanced the official example here with some Mousetrap functionality. So whenever somebody presses alt+1, the first input field will focus, whenever somebody presses alt+2 the second input field will be focused. It works.
Problem:
However, the input field then also takes the value of whatever was pressed as the hotkey (alt+1 then renders to ¡, alt+2 renders to € in the input). But I just want this to be a hotkey, I don't want it's actual value in the input field. How do I do this?
I could clear / delete the input field completely. This would work in the example here, but I don't want to do it since in my final app the state of the input field will need to be preserved, so I cannot just delete it.
Any advice?
import React from "react"
import Mousetrap from "mousetrap"
export default class CustomTextInput extends React.Component {
constructor(props) {
super(props)
// create a ref to store the textInput DOM element
this.textInput = React.createRef()
this.textInput2 = React.createRef()
this.focusTextInput = this.focusTextInput.bind(this)
}
componentDidMount() {
Mousetrap.bind("alt+1", () => {
this.focusTextInput(1)
})
Mousetrap.bind("alt+2", () => {
this.focusTextInput(2)
})
}
focusTextInput(id) {
// Explicitly focus the text input using the raw DOM API
// Note: we're accessing "current" to get the DOM node
if (id === 1) {
this.textInput.current.focus()
}
if (id === 2) {
this.textInput2.current.focus()
}
}
render() {
// tell React that we want to associate the <input> ref
// with the `textInput` that we created in the constructor
return (
<div>
<input type="text" ref={this.textInput} className="mousetrap" />
<input type="text" ref={this.textInput2} className="mousetrap" />
</div>
)
}
}
I
have you tried event.preventDefault() ?
Mousetrap.bind("alt+1", (e) => {
e.preventDefault();
this.focusTextInput(1);
})
Mousetrap.bind("alt+2", () => {
e.preventDefault();
this.focusTextInput(2)
})
#Dhananjai Pai solution didn't worked for me in the same case.
I know this is very old question, so I will just leave the solution, that worked for me, just if someone needs it.
<input
type="text"
placeholder="Put focus here"
name="something"
id="order-code"
class="form-control barcodeScanner"
>
if (typeof Mousetrap !== 'undefined') {
Mousetrap.bind(['`', '('], function (e) {
e.preventDefault();
$('.barcodeScanner').focus()
}, 'keyup');
}
Adding as third option the 'keyup' event solved the issue with typing inside the input.
Source:
https://craig.is/killing/mice#api.bind
There is a third argument you can use to specify the type of event to listen for. It can be keypress, keydown or keyup.
It is recommended that you leave this argument out if you are unsure. Mousetrap will look at the keys you are binding and determine whether it should default to keypress or keydown.
I have been trying to get how to update an input based on keypress in window with ReactJS. I'm building a basic calculator, and it has only one input. I want the input to always be targeted once a key is pressed and also update the values. I also have another function for validation, so the keypress function will still pass through the validation function. Thank you!
I have something like this:
window.onkeypress = function(e){
this.inputFocus(); //my custom function to position the cursor in the input
this.setState({display: this.state.display + e.key});
}
I just don't know the proper location to fit it in cos it says syntax error: unexpected token, pointing at the "." between "window" and "onkeypress"
If you have focus on the input field, by pressing a button, it should enter the value into that input field, and so all you should need to do is make your input a controlled component by doing the following
export default class YourClass extends Component {
constructor(props) {
super(props);
this.state = {
numberValue: 0
}
}
updateValue = (numberValue) => {
this.setState({numberValue})
}
render() {
return (
<input
type='number'
value={this.state.numberValue} // this is initialized in this.state inside the constructor
onChange={(input) => this.updateValue(input.target.value)}
/>
)
}
}
So whenever you change the value, it will automatically update the state, which then sets the value of your input. This is all predicated on you having focus on that input though, so make sure it has focus.
You can assign focus by referencing the element through javascript like...
document.getElementById('yourElement').focus()
OK here it is... I called the window.onkeydown function outside the class as it doesn't update any state. And I later realised that window.onkeydown targets and add the values in the input
import ... from ...;
window.onkeydown = function(e) {
input.focus()
}
class App extends ... {
...
}
export default App;
There is a <div> and a couple of nested <input>s. onBlur fires every time user clicks on one of the <input>s.
This is a bit frustrating that onBlur happens when I hit something inside the div. After an hour of searching I still wasn't able to find any good solution.
This sample of code shows what I'm talking about:
class Thing extends React.Component {
handleBlur(e) {
console.log('blur');
}
handleFocus(e) {
console.log('focus');
}
render() {
return (
<div onFocus={this.handleFocus} onBlur={this.handleBlur} tabIndex="1">
<div>
<input type="text" value="Hello," />
</div>
<div>
<input type="text" value="Thing" />
</div>
</div>
);
}
}
You may play around with the code over here.
However my ultimate goal is to make this thing working properly.
Just adding on to this with what I think is the best solution these days.
This ignores blur events by using the Node.contains method to check whether the element is a descendant of that which is already focused.
handleBlur({ currentTarget, relatedTarget }) {
if (currentTarget.contains(relatedTarget)) return;
/* otherwise normal actions to perform on blur */
console.log('blur');
}
handleFocus(e) {
console.log('focus');
}
You may want to ignore extra blur events.
handleBlur(e) {
if (e.target.tagName == "INPUT") {
return;
}
console.log('blur');
}
handleFocus(e) {
console.log('focus');
}
How about splitting the inputs into a single, independent component?
app.js
class Thing extends React.Component {
handleBlur(val, event) {
console.log(val, event);
}
handleFocus(val, event) {
console.log(val, event);
}
data = ['Hello, ', 'Thing'];
render() {
return (
<div tabIndex="1">
{this.data.map((v, i) => <Input value={v} key={i} onFocus={this.handleFocus} onBlur={this.handleBlur} />)}
</div>
);
}
}
Input.js
import React from 'react';
export class Input extends React.PureComponent {
handleF = () => {
this.props.onFocus(this.props.value, 'focus');
}
handleB = () => {
this.props.onBlur(this.props.value, 'blur');
}
render() {
return <input type="text" onFocus={this.handleF} onBlur={this.handleB} />;
}
}
export default Input;
https://codesandbox.io/s/J6o5Ey9Jg
This is from OP comments above, he found the following solution which worked for him (and now me) and posted it in a comment. I am reposting it here for anyone else who might not think to dig through comments.
Basically, I just check on every blur event if e.relativeTarget has e.currentTarget anywhere in the parentElement chain.
https://codesandbox.io/s/vo2opP0Nn?file=/index.js
Have been playing around with react. Have two event listeners the input which listens onChange and the button which should push the value to the array when its clicked.
Here's the code:
import React, { Component } from 'react';
let arr = [];
class App extends Component {
constructor() {
super();
this.state = {text: 'default'}
}
update( e ) {
this.setState({text: e.target.value})
}
add ( value ) {
arr.push(value)
console.log(arr)
}
render() {
return (
<div>
<h1>{this.state.text}</h1>
<input onChange={this.update.bind(this)}/>
<button onClick={this.add(this.state.text)}>Save</button>
</div>
)
}
}
export default App
The problem that the add function is running on change. Can't really get why.
Any suggestions?
onChange() triggers update()
update() calls this.setState() which changes state.
A state change causes render() to be invoked to re-render according to new state.
Rendering <button onClick={this.add(this.state.text)}>Save</button> invokes add() every time render() runs.
In order to defer invoking add(), you can define a function which gets triggered by the click event, as was shown in another answer. Alternatively, you can achieve the same functionality by adding a class method which encapsulates the trigger functionality:
addText() {
this.add(this.state.text)
}
render() {
…
<button onClick={this.addText.bind(this)}>Save</button>
This may or may not work for you, but in the context of the example, given, this would work.
Change <button onClick={this.add(this.state.text)}>Save</button>
To <button onClick={() => this.add(this.state.text)}>Save</button>
In your variant function add firing when component is rendering, and when you call setState with onChange of input you call this re-render.
The problem is add(this.state.text) is called whenever render() is called. To avoid this, you do not need to send the state as parameter, all you need to do is
<button onClick={this.add}>Save</button
or if you want to send a parameter you should bind it
<button onClick={this.add.bind(this, this.state.text)}>Save</button>
When handling events in a (dump) child component in React, what should be supplied to the callback passed from its (smart) parent component to make it as intended? Should it be the event or only the portion of the result that we are interested in? How does it scale when we have deeply nested components? Are there some other considerations?
Intuitively, I see benefits behind passing the whole event because (i) we can get more data from the event when handling it in the parent and (ii) it separates concerns (the dump components only render and have no logic). On the other hand, it requires the child to have a constructor to bind the wrapper method.
I've seen both approaches used. For example, in Thinking in React the author wraps callbacks in the child component to pass values (see the code on CodePen), whereas in most of the SO posts the event is passed and its value is extracted in the parent component via event.target.value.
Code examples
Pass event:
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({checked: event.target.checked});
}
render() {
return (
<Child checked={this.state.checked} handleClick={this.handleClick}/>
);
}
}
class Child extends React.Component {
render() {
return (
<p>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.props.handleClick}
/>
{" "}
Click me
</p>
);
}
}
Pass value only (notice handleClick2 ):
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(checked) {
this.setState({checked: checked});
}
render() {
return (
<Child checked={this.state.checked} handleClick={this.handleClick}/>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
this.handleClick2 = this.handleClick2.bind(this);
}
handleClick2(event) {
this.props.handleClick(event.target.checked);
}
render() {
return (
<p>
<input
type="checkbox"
checked={this.props.checked}
onChange={this.handleClick2}
/>
{" "}
Click me
</p>
);
}
}
You should pass the thing that you need without the event. There is no need for the whole object unless you want to extract relevant data from the event: for example the target or when you use the same callback for multiple elements/actions.
You won't have any performance problems and there is definitely no react-ish way to do this. Just use your judgement.
event.target is part of the Web Platform standard. For example:
Lets look at an example of how events work in a tree:
<!doctype html>
<html>
<head>
<title>Boring example</title>
</head>
<body>
<p>Hello <span id=x>world</span>!</p>
<script>
function debug(target, currentTarget, eventPhase)
{
console.log("target: " + JSON.stringify(target) );
console.log("currentTarget: " + JSON.stringify(currentTarget) );
console.log("eventPhase: " + JSON.stringify(eventPhase) );
}
function test(e) {
debug(e.target, e.currentTarget, e.eventPhase)
}
document.addEventListener("hey", test, true)
document.body.addEventListener("hey", test)
var ev = new Event("hey", {bubbles:true})
document.getElementById("x").dispatchEvent(ev)
</script>
</body>
</html>
The debug function will be invoked twice. Each time the events's target attribute value will be the span element. The first time currentTarget attribute's value will be the document, the second time the body element. eventPhase attribute's value switches from CAPTURING_PHASE to BUBBLING_PHASE. If an event listener was registered for the span element, eventPhase attribute's value would have been AT_TARGET.
So it would be easy to port to something newer or renewable.
References
W3C DOM4
Accelerated Mobile Pages – A new approach to web performance
Event Handling — Vue.js
AMP Actions and Events: ampproject/amphtml
TypeScript: Using jquery $(this) in event