Note: These notes are taken from Colt steele’s Modern React Bootcamp course
React Events Review
Commonly used React Events
You can attach event handlers to HTML elements in React via special reserved attributes. (You can do this in vanilla JS too, though the syntax is a bit different.)
Event Attributes
Any event you can listen for in JS, you can listen for in React.
Examples:
- Mouse events:
onClick
,onMouseOver
, etc - Form events:
onSubmit
, etc - Keyboard events:
onKeyDown
,onKeyUp
,onKeyPress
- Full list
Example:
WiseSquare.js:
1import React, { Component } from "react";
2import "./WiseSquare.css";
3
4class WiseSquare extends Component {
5 dispenseWisdom() {
6 let messages = [
7 /* wise messages go here */
8 ];
9 let rIndex = Math.floor(Math.random() * messages.length);
10 console.log(messages[rIndex]);
11 }
12
13 render() {
14 return (
15 <div className="WiseSquare" onMouseEnter={this.dispenseWisdom}>
16 š
17 </div>
18 );
19 }
20}
21
22export default WiseSquare;
The Joys of Method Binding š©
The keyword this
:
When your event handlers reference the keyword this, watch out! You will lose the this context when you pass a function as a handler. Letās see what happens when we try to move our quotes into defaultProps.
Example Revisited
WiseSquareWithProps.js
1class WiseSquareWithProps extends Component {
2 static defaultProps = {
3 messages: [
4 /* wise messages go here */
5 ],
6 };
7
8 dispenseWisdom() {
9 console.log("THIS IS:", this); // undefined š
10 let { messages } = this.props;
11 let rIndex = Math.floor(Math.random() * messages.length);
12 console.log(messages[rIndex]);
13 }
14
15 render() {
16 return (
17 <div className="WiseSquare" onMouseEnter={this.dispenseWisdom}>
18 š
19 </div>
20 );
21 }
22}
Fixing our binding
There are three ways to fix this:
- Use bind inline
- Use an arrow function
- Method bind in the constructor
Inline
1<div className="WiseSquare" onMouseEnter={this.dispenseWisdom.bind(this)}>
2 {/* */}
3</div>
Pros:
- Very Explicit
Cons:
Performance Issues:
- What if you need to pass
this.dispenseWisdom
to multiple components? A new function is created on every render
Arrow Functions
1<div className="WiseSquare" onMouseEnter={() => this.dispenseWisdom()}>
2 {/* */}
3</div>
Pros:
- No mention of
bind()!
Cons:
- Intention is less clear.
Performance Issues:
- Again, What if you need to pass
this.dispenseWisdom
to multiple components? A new function is created on every render
In the constructor
1class WiseSquareWithProps extends Component {
2 constructor(props) {
3 super(props);
4 /* do other stuff */
5 this.dispenseWisdom = this.dispenseWisdom.bind(this);
6 }
7}
Pros:
- Only need to bind once!
- More performant
Cons:
- Looks Ugly
HOT RELOADINGwonāt apply.
Note : This method is being used since the introduction of ES6, previously we used to bind this using above two methods. Even though syntax looks ugly, its better to use this approach for better performance.
If calling bind annoys you, there are two ways to get around this. One is Arrow functions, which we already saw . Other method is experimental public class fields syntax, you can use class fields to correctly set bind callbacks.
1class LoggingButton extends React.Component {
2 // This syntax ensures `this` is bound within handleClick.
3 // Warning: this is *experimental* syntax.
4
5 // babel will bind this in a constructor
6
7/*BEHIND THE SCENES ...*/
8
9 // constructor(){
10 // this.handleClick = () =>{
11 // /**/
12 // }
13
14 }
15 handleClick = () => {
16 console.log("this is:", this);
17 };
18
19 render() {
20 return <button onClick={this.handleClick}>Click me</button>;
21 }
22}
Method Binding with Arguments
In our previous examples, this.dispenseWisdom
didnāt take any arguments. But what if we need to pass arguments to an event handler?
Example:
ButtonList.js
1class ButtonList extends Component {
2 static defaultProps = {
3 colors: ["green", "red", "blue", "peachpuff"],
4 };
5
6 handleClick(color) {
7 console.log(`You clicked on the ${color} button.`);
8 }
9
10 render() {
11 return (
12 <div className="ButtonList">
13 {this.props.colors.map((c) => {
14 const colorObj = { backgroundColor: c };
15 return (
16 <button style={colorObj} onClick={this.handleClick.bind(this, c)}>
17 Click on me!
18 </button>
19 );
20 })}
21 </div>
22 );
23 }
24}
Inside of a loop, you can bind and pass in additional arguments. Also possible to use an arrow function, both these approaches suffer from the same performance downsides weāve already seen. We can do better, but first we need to talk aboutā¦
Passing functions to child components
- A very common pattern in React
- The idea: children are often not stateful, but need to tell parents to change state
- How do we send data āback upā to a parent component ?
How data flows
- A parent component defines a function
- The function is passed as a prop to a child component
- Now , the child component has the function and it can invoke it as a the prop
- That calls the parent function , where usually a new state is set or update the exisisting state
- As the state causes the change in the parent state, the parent component is re-rendered along with its children
What it looks like?
Not an ideal way:
NumberList.js :
1class NumberList extends Component {
2 constructor(props) {
3 super(props);
4 this.state = { nums: [1, 2, 3, 4, 5] };
5 }
6
7 remove(num) {
8 this.setState((st) => ({
9 nums: st.nums.filter((n) => n !== num),
10 }));
11 }
12
13 render() {
14 let nums = this.state.nums.map((n) => (
15 <NumberItem value={n} remove={() => this.remove(n)} />
16 ));
17 return <ul>{nums}</ul>;
18 }
19}
NumberItem.js :
1class NumberItem extends Component {
2 render() {
3 return (
4 <li>
5 {this.props.value}
6 <button onClick={this.props.remove}>X</button>
7 </li>
8 );
9 }
10}
- We could also method bind inside of the map
- In fact, we can do even better!
Using a single bound function
BetterNumList.js
1class BetterNumList extends Component {
2 constructor(props) {
3 super(props);
4 this.state = { nums: [1, 2, 3, 4, 5] };
5 this.remove = this.remove.bind(this);
6 }
7
8 remove(num) {
9 this.setState((st) => ({
10 nums: st.nums.filter((n) => n !== num),
11 }));
12 }
13
14 render() {
15 let nums = this.state.nums.map((n) => (
16 <BetterNumItem value={n} remove={this.remove} />
17 ));
18 return <ul>{nums}</ul>;
19 }
20}
BetterNumItem.js
1class NumberItem extends Component {
2 constructor(props) {
3 super(props);
4 this.handleRemove = this.handleRemove.bind(this);
5 }
6
7 handleRemove() {
8 this.props.remove(this.props.value);
9 }
10
11 render() {
12 return (
13 <li>
14 {this.props.value}
15 <button onClick={this.handleRemove}>X</button>
16 </li>
17 );
18 }
19}
Where to bind?
- The higher the better - donāt bind in the child component if not needed.
- If you need a parameter, pass it down to the child as a prop, then bind in parent and child
- Avoid inline arrow functions and binding inside of render if possible -> for performance reasons.
- No need to bind in the constructor and make an inline function -> Do either one of them. I always prefer in the constructor
- If you get stuck, donāt worry about performance, just try to get the communication working
- You can always refactor later!
Naming Conventions
You can call these handlers whatever you want - React doesnāt care
For consistency, try to follow the
action/handleAction
(action
in parent andhandleAction
in child) pattern:- In the parent, give the function a name corresponding to the behavior (
remove
,add
,open
,toggle
, etc.) [send as props] - In the child, use the name of the action along with āhandleā to name the event handler (
handleRemove
,handleAdd
,handleOpen
,handleToggle
, etc.)
- In the parent, give the function a name corresponding to the behavior (
Lists and Keys
BetterNumList.js :
1class BetterNumList extends Component {
2 render() {
3 let nums = this.state.nums.map((n) => (
4 <BetterNumItem value={n} remove={this.remove} />
5 ));
6 return <ul>{nums}</ul>;
7 }
8}
When mapping over data and returning components, you get a warning about keys for list items.key is a special string attr to include when creating lists of elements
When choosing a keyit is important to note that, it need to be unique.
Adding keys
Letās assign a key to our list items inside nums.map():
1class NumberList extends Component {
2 render() {
3 const nums = this.state.nums.map((n) => (
4 <NumberItem value={n} key={n} remove={this.remove} />
5 ));
6 return <ul>{nums}</ul>;
7 }
8}
Keys
Keys help React identify which items are changed/added/removed. Keys should be given to repeated elems to provide a stable identity.
Picking a key
Best way: use string that uniquely identifies item among siblings. Most often you would use IDs from your data [data from db or api etc.] as keys:
1let todoItems = this.state.todos.map((todo) => (
2 <li key={todo.id}>{todo.text}</li>
3));
Last resort
When you donāt have stable IDs for rendered items, you may use the iteration index as a key as a last resort
1// Only do this if items have no stable IDs
2
3const todoItems = this.state.todos.map((todo, index) => (
4 <li key={index}>{todo.text}</li>
5));
A good rule of thumb:
Donāt use
indexesfor keys if item order may change or items can be deleted. This can cause performance problems or bugs with component state.
A goodread: Index as a key is an anti-pattern