ref – http://chineseruleof8.com/code/index.php/2020/06/29/why-we-must-bind-prototype-functions-for-event-handlers-in-react/
create-react-app index-list-example
cd index-list-example
npm start
Go your App.js and change the functional component into a class component.
1) add ‘class App extends React.Component’
2) add constructor
3) add a render prototype function
We then add a state property called list. It’s an array to simply display our list of data.
We put code to map through this list in function render:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class App extends React.Component { constructor() { super(); this.state = { // array of objects // each object has property name, id list: [] }; } render() { return ( <div className="App"> <h3>Dangerous <code>use index as key</code></h3> <form className="form-horizontal"> {this.state.list.map((todo, index) => )} </form> </div> ); } } |
Displaying Item data
Now, let’s write a function called Item that will display each item in the list.
As a function component, it takes the prop as a parameter. We use this prop to get each item from the list.
Then display the properties name/id from each item and display it.
1 2 3 4 5 6 7 8 9 10 |
function Item(props) { return ( <div className="form-group"> <label className="col-xs-4 control-label">{props.name}</label> <div className="col-xs-8"> <input type='text' className='form-control' /> </div> </div> ) } |
then use this Item component in render:
1 2 3 4 5 6 7 8 9 10 11 12 |
render() { return ( <div className="App"> <h3>Dangerous <code>use index as key</code></h3> <form className="form-horizontal"> {this.state.list.map((todo, index) => <Item {...todo} key={index} /> )} </form> </div> ); } |
Add items to the list
First, let’s create a button. Remember that standalone functions (due to being referenced in global environment and being run in strict mode), will dynamically
(http://chineseruleof8.com/code/index.php/2020/06/29/why-we-must-bind-prototype-functions-for-event-handlers-in-react/)
set ‘this’ to undefined, we must bind it correctly.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class App extends React.Component { constructor() { // other code this.addItem = this.addItem.bind(this); } addItem() { const id = new Date().getTime().toString(); this.setState({ list: [...this.state.list, {name: id, id} ] }); } // other code } |
Then in render, we put a button and handler there:
1 2 3 4 5 6 7 8 9 |
render() { return ( <div className="App"> <button className='btn btn-primary' onClick={this.addItem}> <b>Add item</b> to the beginning of the list </button> </div> ); } |
Run it
First, write your name at the top. Then click the button to add items.
When you first write the name ‘ted’, and assigns the index to the key of that component, React will think that ‘ted’ should always appear at index 0.
Thus, as you keep adding items, ted stays at index 0.
However, say we add item to front of the list
Make this code change in prototype function addItem:
1 2 3 4 5 6 7 |
//prototype methods run in strict mode. addItem() { const id = new Date().getTime().toString(); this.setState({ list: [ {name: id, id} , ...this.state.list] }); } |
Now run it again. We put Mike at the box with timestamp of …940:
However, when we append more items to the front of the list, the name Mike gets displayed erroneously index 0, instead of the correct textfield at …940.
This happens because we erroneously assigned array index to our key. As each Items appear, React will assign different index each time these items are rendered. For example, the first time item1 appears, it gets 0. We append item2 on top, so item2 gets 0, item1 gets 1. See? Their keys have changed. ‘mike’ always gets assigned to key 0, and thus, when item2 appears, it appears in item2, instead of item1.
Solution
The solution to this is to use a unique id as key. Notice when we assigned timestamp to our object’s name, we also assign it to id. Timestamp is unique so every entry will be different. This means every entry’s key will be unique. Thus when they get rendered, their respective data will always be matched to the Component Item’s key. Thus, if we were to put ‘ricky’ at an Item1, no matter what kind of Items get appended at anywhere, React will also look for the Component that has the key with the unique ID to insert ‘ricky’ in.
Full Source
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import React from 'react'; import './App.css'; function Item(props) { return ( <div className="form-group"> <label className="col-xs-4 control-label">{props.name}</label> <div className="col-xs-8"> <input type='text' className='form-control' /> </div> </div> ) } class App extends React.Component { constructor() { super(); this.state = { // array of objects // each object has property name, id list: [] }; this.addItem = this.addItem.bind(this); } addItem() { const id = new Date().getTime().toString(); this.setState({ list: [ {name: id, id} , ...this.state.list] }); } render() { return ( <div className="App"> <button className='btn btn-primary' onClick={this.addItem}> <b>Add item</b> to the beginning of the list </button> <h3>Dangerous <code>use index as key</code></h3> <form className="form-horizontal"> {this.state.list.map((todo, index) => <Item {...todo} key={index} /> // todo is {name: , id: } // we pass it in as its own object )} </form> </div> ); } } export default App; |