Callbacks in React
1 May 2019This is one topic in React that I personally like a lot. Passing functions as props is an amazing pattern that I really like! It gives me a sense of control over my components and turns them way more reusable. In this post, I won't talk about performance or readability. Instead, I'll try to clarify a common mistake that I usually read/hear from people in React or other fellow post writers. There are tons of posts on the internet talking about performance issues caused by anonymous arrow-functions passed as callbacks to components' props. This is absolutely true (because of renders and stuff)!! What is NOT true is that there is no workaround when you need to use that function with arguments.
Before proving my point, I'll try to explain some concepts first.
What is an arrow-function, after all?? Well, I can tell what is NOT! Arrow-functions ARE NOT syntactic
sugar for classic functions. They are NOT! I hear this a lot (even from JavaScript developers). Arrow-functions
are not just a way of writing functions without the word function
. Arrow and classic
functions have some differences, being the most important one the fact that the former is a JS function with
lexical scope. This means that the this
is bound to the place where the function is
declared and not where the function is being called. This is one of the main reasons why usually React
developers use the arrow-function (for convenience of course, or lack of understanding ... cof cof). Classic
functions are NOT lexical scoped and that's why you actually need to .bind()
them to
your current this
(it may look like a hack, but is not, is just how things are).
Being so, what's the problem with arrow-functions at this point?? Well, none... The problem is how arrow-functions are used in React as props. Probably if you already used React you should have seen something like this:
What we are doing is passing an arrow-function as a prop from the parent component to the child one. But this arrow-function has a particularity. Is not bound to any reference!! So every time the parent component re-renders the child component also re-renders because the callback property has a new function. Now how can we solve this?
We just give a reference to our function and pass it to the callback, of course!!
Ok problem solved! Now the child component only renders once.
What you need to understand from the two previous comparisons is that when passing a function as property (or
in fact as an argument in JS), passing the reference is all you need to do. It is the child component who is
responsible for calling this function. So when you pass () => { console.log('Hello World') }
or this.onCallback
you are not really calling the functions, you are passing their references.
The misconception that I mentioned at the beginning of the post starts when there are some arguments to pass to the function in the callback. Then, people go back to the arrow-function again despite the loss of performance:
I think the problem here is that people fail to understand how this callback mechanism works. The fact that the
arrow-function is able to capture the arguments (fromChildArg) =>
is because someone
is calling this function somewhere else (in the child component in fact). So why not going back to the
performant version of this code????
Oh… It's working again! What a charm! So let's go to the trickiest part!! “Oh but I need to pass a value to the callback that is bound to the parent component and not the child one!”, they said… and here we go back to the non-performant version:
People assume this has to be this way because code like the following does not work:
What really happens is that this.onCallback(whateverArgumentItDoesntMatter)
is
actually a function being called and not a function being passed to the child component. But if it is so
important to call the function with something bound to the parent component why shouldn't we do this???
Noticed the .bind()
in the contructor??? That is really necessary, because, again,
classic functions don't have lexical scope... The this
used would be from
MyChildComponent
which does not have any state.somethingBoundToParent
...
Evil JavaScript strikes again...
This version still has a referenced function, it is still able to receive something that came from the child component as an argument, and has access to something bound to the parent component. The performance is still there. So there is no such thing as “Sometimes you have to use an arrow-function, there is no workaround”.
Hope you enjoyed it.