So, let's say I have a class named Search that is a simple input field with a submit button. Here's the code for that.
class Search extends Component {
constructor(props){
super(props);
this.state = {term: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
console.log(this);
console.log(e.target.value);
this.setState({term: e.target.value});
}
render() {
return (
<form className='input-group'>
<input className='form-control'
placeholder='City'
onChange={this.handleChange}
value={this.state.value}/>
<span className='input-group-btn'>
<button type='submit' className='btn btn-primary'>Submit</button>
</span>
</form>
)
}
}
So, I need to bind the this keyword to the event handler, handleChange, inside the constructor of the class so that it has the correct reference to this inside the handleChange event handler.
However, if I change the code to the following
class Search extends Component {
constructor(props){
super(props);
this.state = {term: ''};
//this.handleChange = this.handleChange.bind(this);
Line comment above
}
handleChange(e) {
console.log(this);
console.log(e.target.value);
this.setState({term: e.target.value});
}
render() {
return (
<form className='input-group'>
<input className='form-control'
placeholder='City'
onChange={(e) => this.handleChange(e)}
Line change above
value={this.state.value}/>
<span className='input-group-btn'>
<button type='submit' className='btn btn-primary'>Submit</button>
</span>
</form>
)
}
}
Now, if I change the code to the above, I no longer need to do that because I am calling this.handleChange from inside of a callback. Why is this the case?
The reason is that you use an arrow function when you create the callback.
You don't need to bind arrow functions to this, because arrow functions "lexically binds the this value".
If you want, you can change your event handler functions into arrow functions, so that you don't need to bind them. You may need to add the babel plugin 'transform-class-properties' to transpile classes with arrow functions.
If you want to change handleChange into an arrow function, simply change
handleChange(e) {
...
}
to
handleChange = (e) => {
...
}
What you have to consider is what you are expectingthis will evaluate to inside a method and this is something that is not just confined to callbacks.
In the the case of your handleChange method you are referring to this.setState where you are expecting this to be the body of your containing class where it is defined when you create a class extending from React.Component.
When a DOM on-event handler like onClick is invoked, the this keyword inside the handler is set to the DOM element on which it was invoked from. See: Event handlers and this on in-line handler.
As you can infer, there is no setState method on a DOM element, so in order to achieve the desired result, binding the this to the correct scope/context is necessary.
This can be achieved with .bind() or the => (arrow function), the latter of which does not define its own its own scope/context and uses the scope/context it is enclosed in.
Like I said previously the redefining of this is not just confined to DOM on-event callbacks. Another example is when you call the .map() method where you can define the context of this by passing in a context value as a second argument.
Related
There are 4 methods for binding event handlers that i know. The first one is by binding within the DOM element in render():
<button onClick = {this.clickHandler.bind(this)}>Event</button>
The second one is using an arrow function within the DOM element in render():
<button onClick = {() => this.clickHandler()}>Event</button>
The third one is by binding within the class constuctor:
constructor(props) {
super(props)
this.state = {
message: "Click"
}
**this.clickHandler = this.clickHandler.bind(this);**
}
render() {
return (
<div>
<div>{this.state.message}</div>
**<button onClick = {this.clickHandler}>Event</button>**
</div>
)
}
The forth way is by changing the class property to an arrow function:
clickHandler = () => {
this.setState({
message: 'Goodbye!'
})
}
So which way is best?
From what I know :-
In 1st case, a newly bound clickHandler will be created on every render of your React app.
In 2nd case, a new anonymous arrow function will be created which internally calls the clickHandler (on-execution) on every render of your React app.
Both 3rd and 4th are better since they cause one-time creation of the clickHandler. They are outside the render flow.
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
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} />
);
}
}
I have the following input field as below. On blur, the function calls a service to update the input value to the server, and once that's complete, it updates the input field.
How can I make it work? I can understand why it's not letting me change the fields but what can I do to make it work?
I can't use the defaultValue because I will be changing these fields to some other ones
<input value={this.props.inputValue} onBlur={this.props.actions.updateInput} />
In order to have the input value editable you need to have an onChange handler for it that updates the value. and since you want to call a function onBlur, you have to bind that like onBlur={() => this.props.actions.updateInput()}
componentDidMount() {
this.setState({inputValue: this.props.inputValue});
}
handleChange = (e) => {
this.setState({inputValue: e.target.value});
}
<input value={this.state.inputValue} onChange={this.handlechange} onBlur={() => this.props.actions.updateInput(this.state.inputValue)} />
Ways of doing this:
Do not assign value property to input field, whenever onblur method gets trigger, hit the api like this:
<input placeholder='abc' onBlur={(e)=>this.props.actions.updateInput(e.target.value)} />
Update value to server:
updateInput(value){
/*update the value to server*/
}
If you are assigning the value property to input field by this.props.inputValue, then use onChange method, pass the value back to parent component, change the inputValue by using setState in parent, it will work like this:
<input value={this.props.inputValue} onChange={(e)=>this.props.onChange(e.target.value)} onBlur={()=>this.props.actions.updateInput} />
In Parent Component:
onChange(value){
this.setState({inputvalue:value});
}
Update value to server:
updateInput(value){
/*update the value to server*/
}
You will want to bind a onChange event to update your state. Make sure to use the bind method in your constructor so that you do not lose the 'this' context within your onChange event handler method. You will then want to pass the value back to your update input method onBlur. Something like this:
constructor(props) {
super(props);
this.state = {
inputValue: props.inputValue
};
this.handleChange = this.handleChange.bind(this);
};
handleChange = (e) => {
this.setState({inputValue: e.target.value});
}
<input
value={this.state.inputValue}
onChange={this.handleChange}
onBlur={() => this.props.actions.updateInput(this.state.inputValue)}
/>