5

I started using arrow functions after I felt doing manual function/object bindings and scope related issues are headache but very rencently I came to know that it’s better to use normal function(ES5) than arrow function(ES6).

My understanding about these functions

Normal function in React:

  1. Bind object/function manually in order to play with state or props inside the function and to avoid scope related issues
  2. Bind object/function always in constructor but not directly in render
  3. If you do it in constructor then Webpack creates new object/function in bundle.js file only once when your component renders for the first time
  4. If you do it directly in render then Webpack will create a new object/function in bundle.js file every time your component renders and re-renders
  5. If you don’t bind then you can’t access state or props. You have to assign current object to a local variable otherwise this.state or this.props is undefined

Arrow function in React:

  1. No need to bind an object/function in constructor nor render
  2. You no need to depend on local variable interms of current object i.e., let that = this;
  3. You will not have scope issues and object/function binding takes automatically

But my query is that I heard that it’s recommended to use normal function and bind it in constructor rather than using arrow function because arrow functions create new object/function in Webpack bundle.js every time your component renders & re-renders.

Is this true? Which is Recommended?

This thread accepted answer Correct use of arrow functions in React says —> It depends on where exactly are you using the Arrow function. If Arrow function are used in render method, then they creates a new instance everytime render is called just like how bind would work.

Sorry if you feel it’s a theatrical question but this is my biggest doubt. Kindly suggest

Hemadri Dasari
  • 32,666
  • 37
  • 119
  • 162
  • 2
    Don't think I've heard this argument against arrow functions before, could you point to a source issuing this recommendation? – kristaps Aug 27 '18 at 00:12
  • I have seen in one of the stackoverflow answer couple of days before when I was answering but don't remember the source exactly. – Hemadri Dasari Aug 27 '18 at 00:15
  • I don't think it actually matters – maxadorable Aug 27 '18 at 00:16
  • 1
    @auriga it matters because whenever a new object/function created in bundle js file by webpack then your bundle.js file size increases and thats why we are not recommended to do binding directly in render when we use normal function. So keeping that in mind I heard arrow functions always creates a new object.function whenever your component renders and re-renders. This is wr my doubt is – Hemadri Dasari Aug 27 '18 at 00:18
  • @kristaps check this thread accepted answer https://stackoverflow.com/questions/48699573/correct-use-of-arrow-functions-in-react – Hemadri Dasari Aug 27 '18 at 00:31
  • 3
    It depends on how you and where you use them. If you create it inline inside the event handler prop, then it will be recreated regardless if it's arrow or not. If you use **arrow functions as class properties from ESNext**, they will be attached to `this` once like traditional class methods. – Andrew Li Aug 27 '18 at 00:46
  • Thanks Li357 but Eric Kim says it creates a new function only when we do onClick={() => this.onClick()} this way – Hemadri Dasari Aug 27 '18 at 01:07

2 Answers2

11

There are so many answers around there but people always get confused. I know this because I got confused once a while ago. After some time, I grasped the concepts.

  1. Bind object/function manually in order to play with state or props inside the function and to avoid scope-related issues

Not exactly true. You don't need to bind the functions to play with state or props. You bind the function to this when you lose this context in the scope. For example in a callback function.

class App extends React.Component {
  state = {
    name: "foo",
  }
  aFunction() {
    console.log( this.state.name );
  }
  render() {
    return <div>{this.aFunction()}</div>;
  }
}

You don't need to bind your function since this points your class and you don't lose its context. But if you use your function in a callback like a button, you have to bind it:

class App extends React.Component {
  state = {
    name: "foo",
  }
  aFunction() {
    console.log( this.state.name );
  }

  render() {
    return (
      <div>
        <button onClick={this.aFunction}>Click</button>
      </div>
    );
  }
}

This does not work since you lose the context. Now, you need to get its context back somehow right? Ok, let's see how we can do this. First, I want to bind it in the button callback.

<button onClick={this.aFunction.bind(this)}>Click</button>

Yeah, this works. But, it will be recreated in every render. So:

  1. Bind object/function always in constructor but not directly in render

Yes. Do not bind it like I did above, do it in your constructor.

  1. If you do it in constructor then Webpack creates new object/function in bundle.js file only once when your component renders for the first time

  2. If you do it directly in render then Webpack will create a new object/function in bundle.js file every time your component renders and re-render

You are summarizing here what I've tried to explain up to now. But, I suppose Webpack is not the one doing this, your App is.

  1. If you don’t bind then you can’t access state or props. You have to assign current object to a local variable otherwise this.state or this.props is undefined

Again, if you use your function inside your class scope, you don't have to bind it. If you use this function outside of your class, like a button callback, you have to bind it. This is not related to state or props. This is related to using this.

Your second option for binding is doing the binding in the constructor by using a regular function and the third one is using an arrow function without binding.

Now, arrow functions.

1.No need to bind an object/function in constructor nor render

Yes.

  1. You no need to depend on local variable interms of current object i.e., let that = this;

Yes.

  1. You will not have scope issues and object/function binding takes automatically

Yes.

But my query is that I heard that it’s recommended to use normal function and bind it in constructor rather than using arrow function because arrow functions create new object/function in Webpack bundle.js every time your component renders & re-renders.

Like everybody said, that depends on where you use them.

render() {
    return (
        <div>
            <button onClick={() => this.aFunction()}>Click</button>
        </div>
    );
}

Here, it will be recreated in every render. But if you don't need to pass any argument to it, you can use it by reference.

render() {
    return (
        <div>
            <button onClick={this.aFunction}>Click</button>
        </div>
    );
}

This works as the previous one. So, if you see a () in your render method, this function recreated in every render. Regular or an arrow one, doesn't matter. If you are invoking it somehow, then you are recreating it. This applies to bind in the render like aFunction.bind(this). I see () there.

So, use functions by their references to avoid this issue. Now, the big question is what happens when we need some arguments? If you use an arrow function to pass an argument then try to change your logic.

But is it really important as much? Like @Eric Kim said, optimizing is an issue if you really need it. This is a general suggestion since I've heard this from lots of people. But personally, I am trying to avoid using functions if they will be recreated in every render. But again, this is totally personal.

How can you change your logic? You are mapping over an array with an item and creating a button. In this button, you are using a function that passes item's name to a function.

{
    items.map( item =>
        <button onClick={() => this.aFunction(item.name)}>Click</button>
    )
}

This function will be recreated in every render for each item! So, change your logic, create a separate Item component and map it. Pass the item, aFunction as props. Then with a handler function in this component use your function.

const Item = ( props ) => {
    const handleClick = () => props.aFunction( props.item.name );
    return (
        <button onClick={handleClick}>Click</button>
    );
}

Here, you are using an onClick handler with its reference and it invokes your real function. No function will be recreated in every render. But, as a downside, you need to write a separate component and a little bit more code.

You can apply this logic most of the time. Maybe there will be some examples you can't, who knows. So the decision is yours.

By the way, the Medium post that @widged gave in the comments is a famous discussion about this issue. Are arrow functions really slower than the regular ones? Yes. But how much? Not so much I guess. Also, this is true for transpiled code. In the future when they become native, then they will be the faster ones.

As a personal side note. I was using arrow functions all the time since I like them. But a while ago in a discussion, someone said

When I see an arrow function in the class I think that: 'This function is being used/called outside of this class'. If I see a regular one I understand that this function called inside the class.

I really liked this approach and now if I don't need to call my function outside of my class I am using a regular one.

devserkan
  • 16,870
  • 4
  • 31
  • 47
  • 1
    Awesome. You cleared many of my doubts. Much appreciated. Great explanation buddy. Thanks for spending your valuable time and your answer is now really valuable to me :) – Hemadri Dasari Aug 27 '18 at 02:27
  • You're throwing the word "scope" around very liberally in this post. I think what you're looking for is "context". When you exclusively use `this` inside a lifecycle method or some other bound method, `this` context is the component. The reason you even need to bind in the first place is when you pass a function as a first-class object via an event handler, the `this` **context** is lost, (since you aren't calling `this.someMethod` anymore). – Andrew Li Aug 27 '18 at 04:32
  • @Li357, you are totally right. It's my fault, poor wording. The scope is there, we are losing the context indeed. By the corrections like you've done here, I will learn the terminology better. I've updated my answer, thank you very much. But, I'm a little bit confused here: "When you exclusively use this inside a lifecycle method or some other bound method, this context is the component." If the method is not a bound one and I'm calling it inside the class, is not `this` context still the component itself? – devserkan Aug 27 '18 at 13:06
  • @devserkan Well, it depends. React explicitly binds `this` to its lifecycle methods. As for other class methods, it depends on how you call them and if you provide context yourself. If you do, then `this` will be the component, but if not (ie when React calls your event handler internally), it is not provided. – Andrew Li Aug 27 '18 at 13:08
  • I understand the situation for lifecycle methods. For other methods, for example, there is a method just logging a state variable and I'm calling this method from `componentDidMount`. Bound or not, isn't it context the class itself here? Your "some other bound method" phrase confused me, that is all. – devserkan Aug 27 '18 at 13:12
  • Ah! Since lifecycle methods already bounded and we are calling our method from one of them, then this method will be bounded already! Or no need to bound again, right? – devserkan Aug 27 '18 at 13:16
1

If you have following React code,

class A extends React.Component {
  constructor(props) {
    super(props)
    this.state = {name: props.name}
  }
  render() {
    return (
       <button onclick={(event)=>console.log(this.state.name)} />
    )
  }
}

change to following

class A extends React.Component {
  state = {name: this.props.name}
  render() {
    return (
       <button onclick={this.logName} />
    )
  }
  logName = (event) => {
    console.log(this.state.name)
  }
}

This way, you are not creating new anonymous functions every render.

New instance of function is created everytime you have code running ()=> , this is no magic. Take a look at following member functions

class A {
  memberFuncWithBinding = () => {}
  memberFuncWithoutBinding(){}
}

Both of these member functions are only created once, when class is instantiated. Again, no magic, but upper member function is preferred since when using this inside that function, you will have correct class A this binding.

EDIT: Look, DON't try to optimize your code before you run into problems. creating new functions every render is slower but only by fraction of millisecond.

Eric Kim
  • 10,617
  • 4
  • 29
  • 31
  • Thanks. Please take a look at this thread accepted answer https://stackoverflow.com/questions/48699573/correct-use-of-arrow-functions-in-react – Hemadri Dasari Aug 27 '18 at 00:37
  • Are you looking for more clarification or want to close this question as duplicate? – Eric Kim Aug 27 '18 at 00:44
  • Ok what if I want pass arguments to the function like onclick={(event) => this.logName(event)}? Do you mean this also creates a new function only once? – Hemadri Dasari Aug 27 '18 at 00:46
  • if you have that piece of code inside `render` function, it will create new function every render. Currently there is no way to pass different arguments to the member Function with simple reference like onclick{this.someFunc}. this.someFunc will be able to execute different code if it depends on the variable coming from somewhere other than param, such as this.props. or this.state – Eric Kim Aug 27 '18 at 00:49
  • I want to understand which is recommended? Arrow functions are advanced to Normal function. So if arrow functions are recommended and they don’t create a new function everytime your component renders I am fine. So just trying to the possibilities and recommendations – Hemadri Dasari Aug 27 '18 at 00:49
  • arrow functions are only recommended because of automatic binding, not because it won't create arrow function when you call `()=>` – Eric Kim Aug 27 '18 at 00:51
  • Ok if that is the case then can I use normal function and bind it in constructor to get the event? Will this create a new function every time your component renders? This question is to your second comment – Hemadri Dasari Aug 27 '18 at 00:52
  • Hey, I misunderstood your question. if you have `onclick={(event) => this.logName(event)}` then just do onclick={this.someFunc} someFunc = event => { } There is no reason to use normal function over arrow functions. – Eric Kim Aug 27 '18 at 00:55
  • Ok. But when we use some external libraries say suppose that component onClick gives me event, id, name onClick={(event, id, name) => this.onClick(event, id, name)} so here I have no other way I need to get id, name and pass it to event handler function as params. In this case how does it work? Sorry for too many questions but I am trying to understand the concept for every scenario – Hemadri Dasari Aug 27 '18 at 01:04
  • 4
    Be aware that the arrow function (property initializers) option is is still experimental, and not part of ES6. You will find further discussion on this topic in the react issues -- https://github.com/facebook/react/issues/9851. I personally find it best to avoid arrow functions as property initializers. I like to use arrow functions to keep as much of my code without any reference to this... and I have issues with the fact that arrow functions in that specific contexts have a reference to this. – widged Aug 27 '18 at 01:11
  • 1
    For completeness, here is what arrow functions as property initializers look like after transpilation: https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1. – widged Aug 27 '18 at 01:13
  • Some people cite the repetition as the main reason to not use bind in the constructor, but if you have many functions to bind, you can write something of the like in the constructor `this.bound = ['onInteraction1','onInteraction2'].map((k)=> { return this[k].bind(this); })` and in the render function `const {onInteraction1, onInteraction2} = this.bound` and ``. – widged Aug 27 '18 at 01:38
  • 1
    Thank you Eric Kim and widged for spending your valuable time in explaining the concept :) – Hemadri Dasari Aug 27 '18 at 02:31