Cover image by Miguel Discart, on Flickr
At ReactConf the team around React presented a new way to implement interactive components with React called hooks.
They published an RFC so React developers could discuss if this was a good idea.
In this article, we look into how such a functionality could be implemented.
What
Hooks are functions you can call inside your functional components to get the functionality, you would typically only get with component classes.
Why
The basic idea behind hooks is to simplify React development in general, but I won't go into detail, you can read more about it from Dan Abramov, a React core developer, here.
Disclaimer
Read the docs first!
This is an ALPHA feature of React and should not be used in production code.
In this post, we won't use React, but a few lines of code to illustrate how hooks could work.
How
Many people think hooks are magic and go against the philosophy of React and I can't blame them. If we look at the example, it doesn't tell much about what's happening.
import React, {useState} from "react";
function CounterButtton(props) {
let [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}
It uses a simple function call to useState and somehow manages to get us the current state and allows us to change it and rerender the component with the new value.
The JavaScript pros will see the culprit here: globals!
If the useState function doesn't meddle with call stacks to get access to our calling component function, it has to store the data globally.
And if you read Dan's article you may find this tweet:
- JavaScript is single threaded, if someone clears the global before calling our hook function, we will write in a fresh global and nobody can do something while our function runs as long as we only make synchronous calls.
- React calls our functional component so it has control over what happens before and after that call.
Hooks Example
Below, I’ve tried to write a simple example that illustrates how we could implement the "magic" of hooks. This has nothing to do with the official React implementation, but rather, it shows how the idea works.
First, we have some component definition:
function NumberButton() {
let [valueA, setValueA] = useState(0);
let [valueB, setValueB] = useState(100);
return {
type: "button",
props: {
children: `A:${valueA} B:${valueB}`,
onClick() {
setValueA(valueA + 1);
setValueB(valueB - 1);
}
}
};
}
The NumberButton function calls the useState function, which has the same interface as Reacts useState function.
It returns an object that is the definition of a <button> element with some text and a handler.
The function that renders everything into the DOM looks like this:
function run(components, target) {
let savedHooks = new Map();
render();
function render() {
target.innerHTML = "";
components.forEach(function(component) {
globalHooks = savedHooks.get(component);
if (!globalHooks) globalHooks = new Map();
renderToDom(component, target);
for (let [id, hookData] of globalHooks.entries()) {
hookData.calls = 0;
hookData.render = render;
}
savedHooks.set(component, globalHooks);
globalHooks = null;
});
}
}
function renderToDom(component, target) {
let { props, type } = component();
let element = document.createElement(type);
element.innerHTML = props.children;
element.onclick = props.onClick;
target.appendChild(element);
}
It takes an array of components and a DOM element as a render target.
It can only render flat lists of components, no nesting possible, to keep things simple. It also doesn't do any DOM diffing.
- It creates a local variable
savedHooksto store the state of all hooks. - It calls its local
renderfunction to do the actual rendering. - The
renderfunction clears thetargetDOM element and loops over the array ofcomponents. -
Here is where the magic happens: The
globalHooksvariable is overridden right before the component function is used, either with already stored data from the last run or with a freshMapobject. - The component does its thing, like calling the
useStatefunction. - The
hookDatastored by our components call touseStategets a reference to the localrenderfunction so it can initiate a re-render and itscallsattribute is reset. - The
globalHooksdata is saved locally for the next run. - The
globalHooksis set tonull, if there was a next component it couldn't access our data via theglobalHooksanymore.
The actual hook function looks like this:
let globalHooks;
function useState(defaultValue) {
let hookData = globalHooks.get(useState);
if (!hookData) hookData = { calls: 0, store: [] };
if (hookData.store[hookData.calls] === undefined)
hookData.store[hookData.calls] = defaultValue;
let value = hookData.store[hookData.calls];
let calls = hookData.calls;
let setValue = function(newValue) {
hookData.store[calls] = newValue;
hookData.render();
};
hookData.calls += 1;
globalHooks.set(useState, hookData);
return [value, setValue];
}
Let's go through it step-by-step:
- It gets a
defaultValuethat should be returned on the first call. - It tries to get its state from the last run from the
globalHooksvariable. This is aMapobject set by ourrunfunction before our component function is called. Either it has data from the last run, or we need to create our ownhookData. - The
hookData.storearray is used to store the values from last calls and thehookData.callsvalue is used to keep track of how much this function has been called by our component. - With
hookData.store[hookData.calls]we can grab the last value stored by this call; if it doesn't exist we have to use thedefaultValue. - The
setValuecallback is used to update our value, for example when clicking a button. It gets a reference tocallsso it knows to which call of thesetStatefunction it belongs. It then uses thehookData.rendercallback, supplied by therenderfunction, to initiate a re-render of all components. - The
hookData.callscounter is incremented. - The
hookDatais stored in theglobalHooksvariable, so it can be used by therenderfunction after the component function returned.
We can run the example like so:
let target = document.getElementById("app");
run([NumberButton], target);
You can find a working implementation with example components on GitHub
Conclusion
While the approach I took for implementation is far away from the actual React implementation, I think it demonstrates that hooks aren't crazy dev magic, but a smart way to use JavaScript constraints, that you could implement yourself.
My First Book
In the last months I didn't blog as much as before. That's because I wrote a book about learning the basics of React:
If you like understanding how React works by taking it apart, then you might like my book React From Zero. In the book, I dissect how React works by examining how components work, how elements are rendered, and how to create your own Virtual DOM.

Top comments (2)
Good article!
Glad you like it :)