Deconstructing the Magic Behind React Hooks – Hacker Noon

Let the UnMagicking Begin!

Hooks require to be called in the same static order during every render.

The first factor that people notice is that (as mentioned in the Rules for Hooks), hooks are very fussy about their call order. Specifically, React mandates that on every render, the order in which the hooks are called must be the same. This means that you can not call hooks inside loops or conditions, and instead they have to be called at the top-most level.

The reason behind this is quite simple. Consider a few hooks defined in a function component like this:

const [firstName, setFirstName] = useState('John'); 
const [lastName, setLastName] = useState('Doe');
const [age, setAge] = useState(28);

As you can see, this is a plain ES6 call to a function called useState. The only information that the useState function ever gets is the initial value that is passed into it (well not exactly, but we’ll get back to that later). The setState call then returns an array in the form of [value, setter].

On subsequent renders, the same useState method will return the updated state value for that particular property. In order to do this, the useState method will have to somehow keep track of each property requested. However, there is absolutely no place where we are passing a key of any sort to the useState method.

So how exactly does useState know what state to return for which call? Well useState uses the call sequence number inside the render function to track the state. So in the above example the first call to useState will always return the value of firstName and it’s setter, the second call will always return the value of lastName and it’s setter and so on and so forth.

If like me, you are probably thinking that there would have to have been a much better way to implement this, instead of relying on call order, which seems quite hacky. Sebastian Markbage left an excellent comment on the React Hooks RFC, addressing that amongst other things. You should probably go read that too.

Anyway, the short of it is that if you’re using hooks, you absolutely must call the hooks in the same order on every render, or Bad Things Will Happen, with React thinking you want to get or set a certain state value, when the one you want is entirely different.

An easy way to understand this is to think of useState as maintaining a key-value map linking the call order to the state value. On the first render, useState initializes the key-value map as:

const [firstName, setFirstName] = useState('John'); // internal state: {0: 'John'}
const [lastName, setLastName] = useState('Doe'); // internal state: {0: 'John', 1: 'Doe'}
const [age, setAge] = useState(28); // internal state: {0: 'John', 1: 'Doe', 2: 28}

On the second render, useState already has a key-value map, so instead of setting values on it, it just retreives the values based on the call order. Please note, useState does not actually maintain a simple key-value map, and the actual implementation is a bit more complicated than that. But for the purpose of understanding why the call order matters, you can think of it as effectively being a key-value map.

Hooks can only be called from within other hooks or function components.

Another big rule of hooks, is that you can only ever call a hook from a React function component, or from within other hooks. If you think about it, this makes complete sense — if your hook is going to have to get or set the state of a component, or trigger side-effects, either directly or indirectly, then it needs to be linked to a specific React component, to which that state would belong. Otherwise, how would React know which component’s state your hook is trying to access?

So far all seems good. There’s nothing really magicky about this one is there? Well if you want to see the magic, you need to break this rule, and call a hook from inside a normal Javascript function.

function catchMeIfYouCan(){ 
const [firstName, setFirstName] = useState('John');
};

Running this code immediately throws an error:

Uncaught Error: Hooks can only be called inside the body of a function component.

Now hold on a minute! If the useState call is just a normal function call, and a function in Javascript can’t really do much in the way of analyzing the method that called it, how did React know that the useState method was being called from outside a React component? Magic? Not really, but since this ties into the next magicky item on our list, I’ll address them both together.

The useState hook appears to track the state of the component, which was never passed to it.

Consider two components Student and Teacher, both of which have the state properties firstName, lastName and age.

class StudentComponent extends React.Component{
constructor(props){
super(props);
this.state = {
firstName: 'Bobby',
lastName: 'Tables',
age: 8
}
}
render(){
return
{this.state.firstName} {this.state.lastName} ({this.state.age})

}
}
class TeacherComponent extends React.Component{
constructor(props){
super(props);
this.state = {
firstName: 'John',
lastName: 'Keating',
age: 34
}
}
render(){
return
{this.state.firstName} {this.state.lastName} ({this.state.age})

}
}

In the above example, in both of the components, state is an instance property, and therefore can be accessed from within any of the class methods. Since it is an instance property and not a commonly shared object, the values are also encapsulated correctly and TeacherComponent cannot access the state of StudentComponent and vice-versa.

Now let’s convert these components into their function component counterparts using the useState hook.

function StudentComponent(props){
const [firstName, setFirstName] = useState('Bobby');
const [lastName, setLastName] = useState('Tables');
const [age, setAge] = useState(8);
    return 
{firstName} {lastName} ({age})

};
function TeacherComponent(props){
const [firstName, setFirstName] = useState('John');
const [lastName, setLastName] = useState('Keating');
const [age, setAge] = useState(34);
    return 
{firstName} {lastName} ({age})

};

If you notice here, in both of the components, we are calling the same useState method, which is a method imported from the React library, and commonly shared amongst both the components. Like we discussed earlier, useState uses only the call order to keep track of the state values.

Given this, if we render StudentComponent first, setting the firstName, lastName and age state properties, and then render TeacherComponent immediately after it, then the TeacherComponent should be able to access the state values set by the StudentComponent right?

Well actually no. Even whilst using hooks, React ensures that the state values are always properly encapsulated, and cannot be accessed by the wrong component. Otherwise, life wouldn’t exactly be pleasant, with every component being able to access the state of all other components, would it?

But given that we are no longer using this to encapsulate the state within an instance of a component, how can React perform this encapsulation for us? The answer to that is actually quite simple. But before we get to it, we need to make a slight detour into the internals of React. Or rather, the internals of React-DOM.

read original article here