React.js useCallback Hell! and how we can prevent it with knowing pure functions and side-effects in JavaScript + two solutions.

Max Shahdoost
4 min readApr 15, 2022

If you are working with or learning React.js then you must already know the famous performance optimization hooks called useMemo and useCallback already.

Most of us use them to prevent multiple re-render issues on child components by caching the values inside a callback and giving them dependencies for those variables that we know would change during the component life cycle, so with that at hand, we can make sure if any other states change during the life cycle then our children components wouldn’t get another re-render and it’s cached in our JS app.

In this article, I want to introduce a common but very deadly bug that can be caused by this kind of approach in a React.js Application and how we can solve it easily.

Main Component

Imagine having a component like the above where you are using another component inside it as a child which you are passing it a change callback function whose job is to set a state inside itself based on another from outside, so this is definitely not a pure function.

Now let’s take a look at the child component:

Child Component

As you can see, we are having another component that accepts a change callback but let’s take a closer look! wow! in here we are using the famous useCallback hook from React which caches our callback and runs it whenever it is called onChange.

So what is the problem here? can you guess? pause your reading and think about this for a while!

Two components together

Now let’s see what is going on if we run the code!

The random state is a string type which is “firstTry” on the component mount cycle but we have a button where we can change it to “second”, well pretty straightforward and simple so far!

I am starting to type something on the child component which is an input element, I would expect the parent component to be showing me the text I am writing and it does!

Now let’s change the random so I click on the button and change it to “second” and then I see that the random state is immediately changed on the UI, so far so good!

I start to type again and I expect to see the new random state be merged with my typing text but wait a minute what is going on!? why it is still showing me the old “firstTry” state!?

Results on the UI

Well, this is the point, here is where you get bitten by the useCallback bug and it can get really difficult to figure it out when you are dealing with much more complex components and logic inside React rather than this simple example.

The problem is that your handleChange call back in the parent component is cached by useCallback once and only in the component mount cycle with all its javascript context and it won’t change during the component life cycle therefore the random string is trapped in the context of handleChange which is cached in useCallback and it will only return that context even every time you change it!

Solutions:

1- Invest some time in use cases of useCallback whenever you want to use it to check if it is worth using it or not at all for example here we can simply remove it and use the normal arrow function instead.

2- Whenever you want to add side-effects to your callbacks you must pass the callback itself to the dependencies array which is going to let the useCallback know that if the context of your function is changing then it must re-construct your function again.

Conclusion:

If you want to add side-effects to your functions please make sure that you won’t introduce bugs into your code by using caching methods inside React.

Wish you all a great coding journey.

Mohammad Tat Shahdoost.

--

--

Max Shahdoost

A highly passionate and motivated Front-End Software Engineer with a good taste for re-usability, security and developer experience.