React Hooks are a new pattern introduced in React 16.8 that lets you use state, side effects, and multiple features using functional components.
Let’s simplify this with an example.
Notice how you can use variableOutside inside innerFunction? That's basically a closure, because the value, in this case, Hello World is available and locked in the nested function. Pretty simple, right? Remember that you can use as many nested levels as you want.
Now let’s try to create a clone of the useState hook by following the same pattern.
Looks like the value is still the same. This is one form of the stale closure problem. When we destructure value from the output of useStateClone, it refers to the state as of the initial state call and never changes again. This is not the expected functionality that we want; we need our state to reflect the current state even when we update it.
This can be easily fixed, by moving our state inside another closure to keep tracking the state and the changes for it.
Here we create a main closure by defining the ReactClone module. This module, like React, stores and keeps tracking the component state. In this example, we are just tracking a single state for a single component using the variable state.
We also implemented the render function to simulate the react re-renders when making changes to the application as normally React does.
Now the useStateClone hook is defined internally in the ReactClone closure and since we define our state outside of it we can now read it and track it. Notice how we finished the implementation of useStateClone to follow the same interface defined by React, allowing now to update the state by setting a new value or providing a function that calculates the new value by using the old one.
You can use the clone and now effectively see the changes in real-time as you want. Obviously, the React implementation contains more logic and communication with the React reconciler in order to update our components automatically by changing the state but this is the simplest way to get the same feature without any dependency.
In this section, we are going to add a new hook for our clone to provide the useEffect functionality.
Let’s break it into parts. First, we need to define the interface, so it’s okay to receive effect a function that can be executed for side effects in the component, and also an optional effectDependencies array of variables that would make you effect re-run.
To be able to execute the effect, we need to define a storedDependencies variable in the outer closure to keep tracking the changes and in that way decide whether or not to run the effect. Now, we have four different scenarios:
The first one is when we are rendering our component for the very first time. In this case, we are always going to execute our function at least once.
The second one is the case where we only provide the effect without a dependencies array. In this case, hasNotDependencies will always be true and our effect will be called each time the component re-renders matching the exact behavior for the real hook.
The third case will allow us to receive an empty array of dependencies to execute the effect only once no matter if the component re-render multiple times. This is because the function is going to execute it the first time and the storedDependencies will be an empty array for the coming re-renders and hasSomeChanges will be always false since the stored array and the effect one will be basically the same.
The last case is when you provide a variable into the effectDependencies, so in this case, the effect will be executed the first time but when the component re-renders this will be only called again if a new value for the array of dependencies is different from the last render because we are always comparing the last dependencies stored with the new values provided in each render. So the new values become the old ones, and so on.
You can also notice that we are keeping state and storedDependencies in the ReactClone module to keep track of the changes for useStateClone and useEffectClone. But what if I want to have multiple states or run several side effects? Even better, what if I want to have states and effects in multiple components?
Let’s see how we now keep a hooks array to store whether the state for a useStateClone hook or the dependencies for the useEffectClone hook. Using this pattern we can have as many hooks as we want. In order to follow the execution, we are going to store hookBeingExecuted initialized always to 0. Notice also that for each render we want to reset the value to 0 again to start the process again over and over.
The useStateClone implementation is now a little bit different since now we need to get the position for the current state hook being executed and stored in a myPosition variable to create a closure for setState function. This is because we are going to use setState function after the definition of the hooks, and since the hookBeingExecuted is global we will probably update the state of a different one. For that reason, each hook needs to create a closure for its position no matter the global state. Finally, we need to update hookBeingExecuted for the next hook and so on.
In the case of useEffectClone, the change is pretty simple too. We just need to create the closure for the current position, which is pointing to the dependencies for it and at the end just increments the hookBeingExecuted for the next hook.
After reviewing the implementation you can see that there is no magic and no libraries here. Now you can also better understand the rules defined by React about hooks. For example, let’s review one: Don’t call Hooks inside loops, conditions, or nested functions. Makes sense, right? We are using an array and an index value to track the execution of the hook. But if you use it conditionally, you can’t know the current position inside of that array. That’s because they need to be executed in the same order as defined in the component.
The reader can also try to complete the implementation with other hooks like useRefuseReducer. Also, notice that the code examples were simply because we wanted to explain the concepts behind them, but the real React implementation has a lot of more details, conditions, and a connection with the entire system in order to provide all the features.
We’d love to learn more about your project.
Engagements start at $75,000.