A tiny useState
Implement createHooks(). It returns { useState, render }. render(component) sets the hook cursor to 0, calls component(), remembers the component, and returns its result. useState(initial) returns [value, setValue] for the hook at the cursor and moves the cursor forward. State is stored by position, so the same order of calls gives the same state on every render. initial may be a function; it runs only for the first render. setValue takes a value or a function of the previous value; when the value changed (Object.is), it renders the component again, once. Each test names a scenario and shows what the component saw on every render.
Start from
function createHooks() {
function useState(initial) {
// Return [value, setValue] for the hook at the current position.
}
function render(component) {
// Reset the position, call the component, return its result.
}
return { useState, render };
}Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1
Keep an array of states and a cursor in the closure. render resets the cursor; each useState call reads its index and then increments the cursor.
Hint 2
The setter must remember its own index (capture it in a const), not read the moving cursor.
Hint 3
In the setter: compute the next value, stop when Object.is says it is the same, otherwise store it and call render with the remembered component.
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
JavaScript
function createHooks() {
const states = [];
let cursor = 0;
let current = null;
function render(component) {
current = component;
cursor = 0;
return component();
}
function useState(initial) {
const index = cursor++;
if (!(index in states))
states[index] = typeof initial === "function" ? initial() : initial;
const setValue = (next) => {
const value = typeof next === "function" ? next(states[index]) : next;
if (Object.is(value, states[index])) return;
states[index] = value;
render(current);
};
return [states[index], setValue];
}
return { useState, render };
}What an interviewer looks for
The best answer to 'why can hooks not be called in a condition?'. State is found by call order, so skipping one call shifts every later hook onto the wrong state.