React Hooks with closures: useState v/s useReducer

Are you new to hooks and struggling to understand closures affecting functional components and thereby, hooks?If the answer is — Yes, then you have come to the right post.

So, lets start with the basics:

useState

useState is the most commonly used of all the built-in hooks. It is similar to this.setState, we use it to set the state(s) of our functional component.

Attaching a simple code snippet below:

For more detailed information about useState, click here.

useReducer

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

Attaching a simple code snippet below:

For more detailed information about useReducer, click here.

Closure

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function’s scope from an inner function. In JavaScript, closures are created every time a function is created, at function creation time.

For more detailed information about closure, click here.

useState v/s useReducer (w.r.t closure)

To understand how closure affects the functional components with hooks and which one to use when, I will be discussing about a hook named useHistory. It is a custom hook which stores the history and lets you use it.

useHistory:

The example that we will be working on today lets us type a username handle and search for it using the github api and gets the user’s information.

It stores the history of the usernames searched and gives you function for previous, next and a specific username you want to search for.

Let me show you first a demo of the useHistory hook:

Code snippet of useHistory using useState without callbacks and useEffect hook:

Let’s now see the issue that we faced using this code, because of which we thought of shifting to useReducer hook.

Now, as you see if we click on ‘prev’ or ‘next’ multiple times in a short span of time, our code goes into an infinite loop. In a very short span of time this would lead to memory leak and would crash the browser.

As you can see, in our code in the useEffect hook function we are not changing any state variable(in case of success: loading, currentItemDetails, in case of error: loading, error) that leads to re-rendering(currentItemNo). But somehow these variables(currentItemNo) are changing. This is the issue where closure comes into picture and in our case is creating a problem.

When the call gets called, then the function inside it gets into the scope chain with state object being saved as the state that it is in currently and after this when the function components gets called again the state object gets different memory reference and hence does not get updated.

Let us see the problem using a flowchart:

Now as you can see, because the currentItem is getting changed on each success/error callback, the useEffect hook will get triggered again and again and this will lead to a infinite loop( or multiple unnecessary calls).

Now, the issue is because of the variable state being initialized inside the functional component, hence whenever it gets called again the variable state gets another reference and hence, because of closure saving the old state we are updating setState with old state.

const [state, setState] = useState({
...initialState,
...data
});

Now, there are few solutions to solve this problem:

  1. Declaring the variables outside the functional component:

Now, since the variables have been declared outside the functional component scope, even when the function gets called again, the memory reference for state variable wont be changes and, hence our functions like setSuccess, setError would get the latest state.

2. Using useState’s setState with callbacks:

Now, when we call setState in the respective functions like setSuccess, setState gives us the latest state and hence when we update it, we will be updating the changes w.r.t latest state.

3. Using useReducer:

Now, as you know useReducer is used when the next state depends on the previous one hence, it will always have the previous state and hence, we do not need to worry about the state function getting re-initialized or saving older value.

Thats all from my side.Thank you for reading my first blog on react hooks with patience. :)