I am using ReactJS with Babel and Webpack and using ES6 as well as the proposed class fields for arrow functions. I understand that arrow functions make things more efficient by not recreating the functions each render similar to how binding in the constructor works. However, I am not 100% sure if I am using them correctly. The following is a simplified section of my code in three different files.
My code:
Main.js
prevItem = () => {
console.log("Div is clicked")
}
render(){
return (
<SecondClass prevItem={this.prevItem} />
)
}
SecondClass.js
<ThirdClass type="prev" onClick={()=>this.props.prevItem()} />
ThirdClass.js
<div onClick={()=>{this.props.onClick()}}>Previous</div>
Question:
Is my code above using the arrow functions correctly? I noticed that for SecondClass.js I could have also used:
<ThirdClass type="prev" onClick={this.props.prevItem} />
Is there a difference between one method or the other since I used an ES6 arrow function in my original function definition? Or should I be using the arrow syntax all the way through until my last div?
I understand that arrow functions make things more efficient by not recreating the functions each time they are referred to
Arrow functions handles the this
context in a lexical way, where "normal" function do it dynamically. I wrote about the this key word in depth if you need more info about it.
On both of your examples of the inline arrow function, you are creating a new function instance on each render
.
This will create and pass a new instance on each render
onClick={() => {}}
On the 3rd example you only have one instance.
This only pass a reference to an already existing instance
onClick={this.myHandler}
class
via this
:
myHandler(){
// this.setState(...)
}
You will need to explicit bind
it to the class
.
The most common approach will be to do it in the constructor
because it runs only once:
constructor(props){
super(props);
this.myHandler = this.myHandler.bind(this);
}
If you use an arrow function as the handler though, you don't need to bind
it to the class
because as mentioned above, the arrow function use a lexical context for this
:
myHandler = () => {
// this.setState(...)
}
With both approaches you will use the handler like this:
<div onClick={this.myHandler}></div>
The main reason for taking this approach:
<div onClick={() => this.myHandler(someParameter)}></div>
Is if you want to pass parameters to the handler beside the native event
that get passed, meaning you want to pass a parameter upwards.
As mentioned, this will create a new function instance on each render.
(There is a better approach for this, keep reading).
Running example for such use case:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
}
}
toggleITem = (itemName) => {
this.setState(prev => {
const nextState = prev.items.map(item => {
if (item.name !== itemName) return item;
return {
...item,
active: !item.active
}
});
return { items: nextState };
});
}
render() {
const { items } = this.state;
return (
<div>
{
items.map(item => {
const style = { color: item.active ? 'green' : 'red' };
return (
<div
onClick={() => this.toggleITem(item.name)}
style={style}
>
{item.name}
</div>
)})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
A better approach would be to create component composition.
You can create a child component that wraps the relevant markup, will have it's own handler and will get both the data
and handler
as props from the parent.
The child component will then invoke the handler that it got from the parent and will pass the data
as a parameter.
Running example with child component:
class Item extends React.Component {
onClick = () => {
const { onClick, name } = this.props;
onClick(name);
}
render() {
const { name, active } = this.props;
const style = { color: active ? 'green' : 'red' };
return (<div style={style} onClick={this.onClick}>{name}</div>)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
items: [{ name: 'item 1', active: false }, { name: 'item 2', active: true }],
}
}
toggleITem = (itemName) => {
this.setState(prev => {
const nextState = prev.items.map(item => {
if (item.name !== itemName) return item;
return {
...item,
active: !item.active
}
});
return { items: nextState };
});
}
render() {
const { items } = this.state;
return (
<div>
{
items.map(item => {
return <Item {...item} onClick={this.toggleITem} />
})
}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Class Fields the down-side:
As i mentioned, there is a small down-side for class fields.
The difference between a class method and a class field is that the class field is attached to the instance
of the class
(constructor function).
where as the class methods and objects are attached to the prototype.
Hence, if you will have ridiculously large amount of instances of this class you may get a performance hit.
Given this code block:
class MyClass {
myMethod(){}
myOtherMethod = () => {}
}
babel will transpile it to this:
var _createClass = function() {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function(Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
}();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
var MyClass = function() {
function MyClass() {
_classCallCheck(this, MyClass);
this.myOtherMethod = function() {};
}
_createClass(MyClass, [{
key: "myMethod",
value: function myMethod() {}
}]);
return MyClass;
}();