Event Handling in React
Event handling in React is similar to how it happens in JavaScript. In order to tackle cross browser compatibility, react has introduced Synthetic Events. It is a wrapper over browser's native events. This makes sure that all events works same across browsers and taking out the need of browser specific event handling.
Before moving ahead, lets understand few react design principles that we need to be mindful of while working with React states and events:
- A component that owns a piece of the state should be the one modifying it
- To handle this, raise an event from the consumer component(s) and let owner component handle the state modifications
- This is required as state is the private property of that specific component, it should be exposed but not to be modified by other components
In react, similar to attributes, events also follow came casing also because of synthetic events. Ex., JavaScript onclick becomes onClick in react.
Raising an Event
Take a look at the below simple example, where we have only one component and it has its own state, and handler methods.
There are 2 ways to attach an event handler in react:
- Pass handler function reference
- Using arrow functions
- Pass handler function reference without parameter
- Take a look at below class component:
- //Parent Componentexport default class ParentComponent extends React.Component {constructor() {super();//Statethis.state = {clickCount: 0,};}//event handler to increment the clickCount,passing a parameter to this handler which is not a event objectincrementHandler = (name) => {//at the moment, lets console logconsole.log(name + " is increasing the counter..");};//event handler to reset the clickCount,passing a parameter to this handler which is not a event objectresetHandler = (name) => {//at the moment, lets console logconsole.log(name + " is reseting the counter..");};render() {const name = "Hardik";return (<><h1>Hello, Total clicks are {this.state.clickCount}</h1><button className="btn" onClick={this.incrementHandler("Hardik")}>Click Now to increment!</button><button className="btn" onClick={this.resetHandler(name)}>Reset Count!</button></>);}}
- Output:
- As you would notice, the handlers run even before the buttons are clicked. After subsequent clicks, nothing gets logged in console.
- This happens because we are calling the function. That function executes only once the moment the component is loaded.
- this in JavaScript behaves differently, it is resolved depending on how the function is called. So, this can refer to different objects!
- For ex., If obj.method() is called, this will always resolve to the reference of that object
- If directly function() is called, by defaults it will resolve the reference to the window object. If strict mode enabled, it will return undefined!
- To fix this, we can leverage either of bind, apply or call methods in JavaScript.
- Now refer the updated example below:
- //Parent Componentexport default class ParentComponent extends React.Component {constructor() {super();//Statethis.state = {clickCount: 0};//return new function with this being set to the object provided as parameterthis.incrementHandler = this.incrementHandler.bind(this);//return new function with this being set to the object provided as parameterthis.resetHandler = this.resetHandler.bind(this);}//event handler to increment the clickCount,passing a parameter to this handler which is not a event objectincrementHandler = (name) => {//at the moment, lets console logconsole.log(name, " is increasing the counter..");const newClickCount = this.state.clickCount + 1;this.setState({ clickCount: newClickCount });};//event handler to reset the clickCount,passing a parameter to this handler which is not a event objectresetHandler = (name) => {//at the moment, lets console logconsole.log(name, " is reseting the counter..");const newClickCount = 0;this.setState({ clickCount: newClickCount });};render() {const name = "Hardik";return (<><h1>Hello, Total clicks are {this.state.clickCount}</h1><button className="btn" onClick={this.incrementHandler}>Click Now to increment!</button><button className="btn" onClick={this.resetHandler}>Reset Count!</button></>);}}
- Output
- We have extended our default setup to increment state value
- Also note that the unlike actual value in name argument in the event handler, it holds value of the react Synthetic Event.
- We clicked the increment 3 times and then clicked reset. It can be seen from the console.log above.
- This method is useful when we dont want to pass any argument but just wants the handler to do the rest.
- Use Arrow functions to pass parameter
- We will extend above function to include a child component and move the rendering logic to it
- Also, we will use props to expose the incrementHandler and resetHandler to child component
- While attaching the events, we will leverage arrow functions and pass the arguments to it to check if it reflects correctly in the console.
- Refer updated code below:
- //Parent Component export default class ParentComponent extends React.Component { constructor() { super();
//State this.state = { clickCount: 0, };
//return new function with this being set to the object provided as parameter this.incrementHandler = this.incrementHandler.bind(this);
//return new function with this being set to the object provided as parameter this.resetHandler = this.resetHandler.bind(this); }
//event handler to increment the clickCount, passing event object and name incrementHandler = (event, name) => { //at the moment, lets console log console.log(name + " is increasing the counter.."); const newClickCount = this.state.clickCount + 1; this.setState({ clickCount: newClickCount }); };
//event handler to reset the clickCount, passing event object and name resetHandler = (event, name) => { //at the moment, lets console log console.log(name + " is reseting the counter.."); const newClickCount = 0; this.setState({ clickCount: newClickCount }); };
render() { return ( <> <h1>Hello, Total clicks are {this.state.clickCount}</h1> <ChildComponent incrementHandler={this.incrementHandler} resetHandler={this.resetHandler} /> </> ); } }
//Functional Component const ChildComponent = ({ incrementHandler, resetHandler }) => { const name = "Hardik"; return ( <> {/* Using arrow function and pass parameter as literal */} <button className="btn" onClick={(event) => incrementHandler(event, "Shah")}> Click Now to increment! </button> {/* Using arrow function and pass parameter */} <button className="btn" onClick={(event) => resetHandler(event, name)}> Reset Count! </button> </> ); };
- Output
- As you could see, we have incremented counter 3 times and badge shows that its logged 3 times
- It also used the argument we passed to the event handler while incrementing or resetting and displayed on the console accordingly.
- Please note that arrow function will have access to event object which is actually a Synthetic event as we have seen earlier and it can be also passed to the our event handler function.
- We will extend above function to include a child component and move the rendering logic to it
- Also, we will use props to expose the incrementHandler and resetHandler to child component
- While attaching the events, we will leverage arrow functions and pass the arguments to it to check if it reflects correctly in the console.
- Refer updated code below:
- //Parent Componentexport default class ParentComponent extends React.Component {constructor() {super();//Statethis.state = {clickCount: 0,};//return new function with this being set to the object provided as parameterthis.incrementHandler = this.incrementHandler.bind(this);//return new function with this being set to the object provided as parameterthis.resetHandler = this.resetHandler.bind(this);}//event handler to increment the clickCount, passing event object and nameincrementHandler = (event, name) => {//at the moment, lets console logconsole.log(name + " is increasing the counter..");const newClickCount = this.state.clickCount + 1;this.setState({ clickCount: newClickCount });};//event handler to reset the clickCount, passing event object and nameresetHandler = (event, name) => {//at the moment, lets console logconsole.log(name + " is reseting the counter..");const newClickCount = 0;this.setState({ clickCount: newClickCount });};render() {return (<><h1>Hello, Total clicks are {this.state.clickCount}</h1><ChildComponentincrementHandler={this.incrementHandler}resetHandler={this.resetHandler}/></>);}}//Functional Componentconst ChildComponent = ({ incrementHandler, resetHandler }) => {const name = "Hardik";return (<>{/* Using arrow function and pass parameter as literal */}<button className="btn" onClick={(event) => incrementHandler(event, "Shah")}>Click Now to increment!</button>{/* Using arrow function and pass parameter */}<button className="btn" onClick={(event) => resetHandler(event, name)}>Reset Count!</button></>);};
- Output
- As you could see, we have incremented counter 3 times and badge shows that its logged 3 times
- It also used the argument we passed to the event handler while incrementing or resetting and displayed on the console accordingly.
- Please note that arrow function will have access to event object which is actually a Synthetic event as we have seen earlier and it can be also passed to the our event handler function.
- As per the the react design principles that we discussed at the start of this article:
- We moved state to the parent component which should have control on modifying its private state - we lifted our state up to the ParentComponent.
- Parent component can expose methods to manipulate those methods to keep the control.
Comments
Post a Comment