An advanced example when closures go wrong

David Polites
4 min readAug 31, 2021
Credit to Sam Balye on Unsplash

Every software developer has run into this. That one problem where you are sitting there, or standing, working on a problem you can’t solve. Ok, maybe not solve, but it’s taking you forever! You know you can solve this! You build the internet! How can this one problem stop you? It’s totally frustrating.

I hate programing.
I hate programming.
I hate programming.
It works!
I love programming.

This is a pretty detailed article to explain a subtlety between React and JS that can trip developers up. Hopefully, other people can take advantage of this principle sooner than I did and save some time.

This code was a recent task that had me working on a custom React hook. The first task on it was to show errors received from a server during a polling process. The trick was that the hook requirements stated a user could “clear” the alert. So, we can’t show it a second time if the error is still happening. For example, a full storage device. Obviously, we need to tell the user we are out of storage, but we also don’t want to show the error over and over again. Let’s show it once, let the user know, and move on. It DOES make sense to show it again though if the error condition is cleared up, then it occurs again. Those are some finer points that add some complication. Ok, seems pretty reasonable.

After the first iteration, it was requested that we also re-display ongoing errors at set intervals. Say, every 30 minutes. This sounds like a pretty straight forward setup. No big deal! Well, that’s when closures got in the way.

I am going to show you the evolution of this hook and how I got to the final answer. With any luck, the principal behind the bug will be visible, and you won’t be spinning your wheels as much as I did initially.

The first snippet here is the original hook. This one receives errors from an outside source. In my case it was a polling mechanism that fired and stored the errors in redux. You can see the redux subscription setup in the Consumer file.

The hook ./useReconcileErrors.ts:

Here is how it’s used ./Consumer.tsx:

This works like a champ when not using the re-display logic.

Redisplay Logic Attempt 1

Like all efforts, developers choose to typically extend what’s already there. We’re lazy by default, right? Especially, if we wrote the original code! So, that’s what I did for this round. There’s a flaw in the code though. See the comments below in the setInterval callback to identify it.

This is the hook version 2 ./useReconcileErrors:

Using this code above, I kept getting the initial error to display fine, but nothing ever showed up on the interval. Why was that? All the errors were being displayed fine. The timer was firing correctly. Errors were not getting cleared out too early. I could see the error getting added to the list to be shown again. What eventually dawned on me was the closure was wrong! The errorsToShow and viewedErrors lists are incorrect by the time the timer callback executes. The closure prevented the current values of those arrays from being used.

When considering more advanced closures, we must pay more attention to what data, or references, are being used in the “snapshot” of the closure. This is a common source of injecting bugs in a very subtle way.

Redisplay Logic Attempt 2

This is where one of the greatest features of Javascript gets in the way. The closure was flat out wrong. React wasn’t helping either. Every alternate solution I came up with involved NOT using React hooks the way they were intended. This means I was now fighting React and not working with it.

The solution turned out to be pretty straight forward even though it took some boiler plate to get done. Embrace the closure, but don’t use data in it. The useReducer hook to the rescue!! Now the closure contains a dispatch function that never changes. That’s it! So now we can embrace React and the Closure as required. Everything works quite well and the number of render cycles actually went down with this approach. Bonus points if it makes sense why that happened :)

Here is the new local reducer logic. Boiler plate deluxe! Sometimes you can’t avoid it though:

Now the hook actually gets more simple:

Replacing the entire state management in the hook was not really something I wanted to do, but it made sense. Switching from useState to useReducer moved the data needs OUT of the setInterval callback and allowed the data to be maintained where it needs to be….a reducer in this case.

In short…watch your closure definitions!

--

--

David Polites

David is a fullstack software developer with 20+ years of experience. He is a tinkerer, reader, amateur writer, and enjoys hanging out with the family.