React Tricks: Get your useEvent before it’s here and the why of it
So I am seeing a new API being on the burner in React Hook offering. useEvent. I was like …
I already was using this at my previous workplace. Well, not the API but something custom that solved this exact problem. Before I go any further let me just quickly drop the solution with a sandbox link for you to try out.
Here is a sandbox for you to test out the hypothesis.
Now you have seen the solution, let’s talk about the problem and then work our way through it to arrive at a solution.
Stale Closure !!!
Ever since React went functional, stale closure is a prevalent issue everyone faces switching to it. I struggled, my lead does so do several other people. So let’s discuss the problem in the context of React. Let’s first refresh closure a bit shall we?
So a closure is basically a function statically bound with its lexical scope. That’s it.
👆 Here is a nice simple example of a closure. The function returned from the createClosure is going to return a new function , which will always be able to reference any variable that was declared inside its creator function. If you remember languages that do not support something like this i.e C, you may see trying to extract values of variables inside functions beyond its stack lifetime will require you to pass it as a pointer or just lead to some garbage value.
Now let’s take a look at it in the context of React
If you know how does React works, you would know every component render method (class component) and function component gets executed to get the final Object tree that represents the state of UI. Which means any inner function I declare inside the component function, will form closure with all the variables in its immediate vicinity right?
Here in the above code onClick is the function forming a closure.
But there is a problem … !!!
The MemoComponent will re-render at every click. Since onClick function changes every time the component is invoked by React. This leads to excessive re-render, at times which is not desirable if your component is doing some costly operation.
Solution ? useCallback at rescue.
Although we made the reference to the function same across re-renders. We are now hitting the issue of Stale Closures.
Remember react invokes your component function to get the current object tree of UI. So our increment function also runs at that point. But the useCallback makes sure there is no new function creation i.e we are still using the same function created on the first render bound with the value of the first render. Hence the name currentLocalScopeValue.
Well just add it to the dependency list of useCallback. Ok but what about our really costly MemoComponent. We cannot afford to have it re-rendered repeatedly right?
So let’s summarise the two problems we want to solve.
- We want a stable reference for a function inside a component
- We do not want to have stale closure
That is where our solution comes into the picture.
We solved both of the problems by a simple trick of using useRef and useCallback in conjunction.
How does it work though ?
Quite simple actually. We use useRef , to hold the current reference to the most latest version of the function. Then we employ useCallback with empty dependency list to call on the reference function. And done !!
We have a stable reference with always the most recent version of the function, with the latest closure.
Ughh more Platform Code?
Well, it depends. If your codebase is huge , I feel something like this makes sense. Your devs need not solve this problem every time. More importantly, they need not care about this problem anymore. Possibly the best part is when React does release and when you do upgrade, the implementation of useEvent itself can be replaced by the actual React one with zero fuss. 👀 or maybe a little. Don’t know, it’s still in RFC stage.