Follow me on Twitter, happy to take your suggestions on topics or improvements /Chris
When you build your component you are going to want to add methods to it. You are going to attach methods to different events such as submit
, click
, change
etc.
One thing you need to keep in mind is that React changes the name and the casing of the event. What happens is that the text on is prepended to the event name. Additionally the casing changes name so that event name is capitalized. Essentially think of events in React to look like so:
on<Event name>
Below is a few examples of some common events:
Event name | Name in React |
---|---|
click | onClick |
change | onChange |
submit | onSubmit |
Hopefully, you get the idea :)
To wire up an event to an event handler a method, you need the name of the event (the React version of its name) and a function. Here's an example of a wireup with a class-based component:
<Component onClick={this.handler}>
This process looks slightly different if you are using a class based component or a function based. Let's cover the former first:
class Element extends React.Component {
constructor(props) {
super(props);
this.clicked = this.clicked.bind(this);
}
clicked() {
console.log('clicked');
}
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
Note two points of interest:
-
the constructor, in the constructor there's a line of code that ensures the method knows what
this
is:this.clicked = this.clicked.bind(this);
The reason we need this is when you want to access properties on the component inside of the
clicked()
method. Had yourclicked()
method looked like this:clicked() { console.log(this.props.someAttribute); }
the code would have printed the value of
someAttribute
, if it was set at bind time. Had you NOT done the row in the constructor you would have gotten an error. -
the JSX. In the JSX part, in the
render()
function, the actual wire up is done:<button onClick={this.clicked}></button>
Thanks to the row in the constructor, you can now refer to
this.linked
, and you don't have to worry about the value ofthis
.
At this point, your code works using the handler.bind(this)
like statement. It doesn't feel all too pretty. You will need to do this for every method that you intend to bind to an event.
Is there a better way? Actually there are two more ways we could solve this:
- Invoke our method as a lambda. This approach means you use an arrow function in your JSX handler expression.
- Declare our method as a field in the class
Let's look at the first mentioned variant. In this version we use a lambda in the set up in the markup. The code looks like so:
class Element extends React.Component {
constructor() {
super();
}
state = {
str: 'test'
}
clicked(evt) {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={(evt) => this.clicked(evt)}></button>
)
}
}
Let's zoom in to the change:
<button onClick={(evt) => this.clicked(evt)}></button>
The difference from the first variant was that you went from using an expression looking like so:
onClick={this.clicked}
to looking like so instead:
onClick={(evt) => this.clicked(evvt)}
With this variant, you can skip the bind
in the constructor. Additionally, it gives you something else, the ability to pass a value to the event callback. In your example you pass the evt
, the event object. You can pass more than that, in fact, any custom argument, like in this example:
render() {
return (
<button onClick={(evt) => this.clicked(evt, 'left')}></button>
<button onClick={(evt) => this.clicked(evt, 'right')}></button>
<button onClick={(evt) => this.clicked(evt, 'top')}></button>
<button onClick={(evt) => this.clicked(evt, 'down')}></button>
)
}
Above you're passing a string left
, right
, top
, down
, just to show that this is a great construct if you want to pass data to the event handler.
This is not part of the ECMAScript standard yet.
In this version we declare our method a little bit differently:
class Element extends React.Component {
constructor() {
super();
this.clicked = this.clicked.bind(this);
}
state = {
str: 'test'
}
clicked = () => {
console.log('clicked ' + this.state.str);
}
render() {
return (
<button onClick={this.clicked}></button>
)
}
}
Notice the difference between declaring the method in the old way, like this:
clicked() {
console.log('clicked ' + this.state.str);
}
Now we instead declare clicked as a field like so:
clicked = () => {
console.log('clicked ' + this.state.str);
}
This is the preferred way of declaring methods on a class.
Wiring up to an event handler in functional component looks slightly different. For one thing, you don't have to worry about the value of this
. Here's an example:
import React from 'react'
const AComponent = (props) => {
function handler(evt) => {
console.log('clicked');
}
return (
<div>
<button onClick={handler}></button>
</div>
)
}
export default AComponent;
As you can see it's a lot easier to deal wiring up events to handlers in functional components.
-
Create a new project by running the command
git clone
:git clone https://github.com/softchris/react-starter-project demo-methods cd demo-methods
This starter project is based on the tutorial in Setup with Webpack.
-
Run
npm install
to install all dependencies:npm install
-
In the src directory, create a file
Methods.js
and give it the following content:import React from 'react'; class Method extends React.Component { constructor(props) { super(props) this.state = { direction: '' } } changeDirection (evt, direction) { this.setState({ direction }) } render() { return (<React.Fragment> <div>Direction: {this.state.direction}</div> <div> <button onClick={(evt) => this.changeDirection(evt, 'Top')}>Top</button> <button onClick={(evt) => this.changeDirection(evt, 'Left')}>Left</button> <button onClick={(evt) => this.changeDirection(evt, 'Right')}>Right</button> <button onClick={(evt) => this.changeDirection(evt, 'Bottom')}>Bottom</button> </div> </React.Fragment> ) } } export default Method;
What you have, is a component that sets up
onClick
event to the handlerchangeDirection()
, that is defined on the class component. -
In the file index.js, add the following import statement at the top:
import Method from './Method';
-
Locate the part code that says
ReactDOM.render(
and replace it with:ReactDOM.render( <Method />, document.getElementById('app') );
-
From the console, start up the app:
npm start
-
In a browser, navigate to http://localhost:8080
You should see the direction state at the top. Select any of the four buttons, and ensure the state is updated.
You were introduced to method and how you could bind them to events. A good practice is to bind the method, that you mean to use for event handling, in the constructor. There's also the option of using a lambda, that means you can skip the binding and you can additionally pass values to the handler.