An Interview Question
Have you been asked questions like this during interviews?:
- Why react hook cannot be written in conditional or loop statements?
(This is a classic question of Chinese style interview, we call it ‘八股文’, which basically means that the questions is too classic and everyone can answer it by remembering it)
In this article, we will learn the software engineering principles behind the scenes. After learning these principles, you will easily understand how other state management tools(redux, jotal) work.
The Rules of Hooks
- Don’t call Hooks inside
loops,conditions, ornestedfunctions
Learning How React Hooks Work by Building a Naive useState
Part 1: What is a State?
A state is nothing more than a
getterand asetter.
You can see the concept of state exists in almost all programming languages. (Some programming languages use stream instead given that the issues state causes)
- It is a powerful abstraction given that we ordinarily view the world as populated by independent objects, each of which has a state that changes over time.
- An object is said to “have state” if its behavior is influenced by its history.
Let’s take an example using Javascript:
Say, we wanna build a bank account system. Whether we can withdraw a certain amount of money from a bank account depends upon the history of deposit and withdrawal transactions.
1 | function bankAccount(deposit){ |
As you can see, in javascript, the state is pretty simple:
init: use keyword let to define a statelet balance = 50;setter: use = to assign(set) a new value to statebalance = 666;getter: use the variable name to get the valuebalance
Part 2: Implement useState
Once you understand the essence of state, you can easily understand react state too.
React state is nothing but a state abstraction with a rerender mechanism in react component.
I will walk through how to build a useState step by step. Before that, let’s sort out what features we need to react state.
init: init a state, in reactuseState()init a state in a component.getter: the first value of return array ofuseState()is thegetter.setter: the second value of return array ofuseState()is thesetter.rerender: when set a new state, it should trigger react componentrerenderusing the latest state.1
2
3
4
5
6
7
8
9
10
11
12
13
14// init a state balance with value 666
// get state using balance
// set state using setBalance method
const [balance, setBalance] = useState(666);
Javascript already provides a state abstraction, and react provides a function render to rerender a component. Let's build our useState upon these abstraction.
let state;
function useState(initialValue) {
state = state || initialValue;
function setter(newState) {
state = newState;
render(<App />, document.getElementById('root'));
}
return [state, setter];
}
Here is how we use our state:
1 | const App = () => { |
Now it works pretty well! But as our react component becomes more complex, we find that this implementation is not sophisticated enough, it will break when there are multiple states are declared.
Multiple States
It breaks when we add a name state:
1 | const App = () => { |
This is because we only store states in a single variable. Now we change it to Array(List), we need a cursor to indicate the index of the current state, so that we can get the correct state:
1 | let state = []; |
Now let’s take a look at what happens when we call the code below:
1 | const [balance, setBalance] = useState(666); |
Why is Order Important?
As you can see, the order is very important because we use array and index to help useState to map to correct state’s getter and setter.
Let’s take a look if we write hook inside a conditional statement:
1 | let firstRender = true; |
As you can see, the name state is mapped to the wrong state using the wrong cursor. This is why we cannot break the order of state execution using conditional or loop statements in react.