Skip to content
React logic · Senior · about 30 minutes

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.

  1. 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.

  2. Hint 2

    The setter must remember its own index (capture it in a const), not read the moving cursor.

  3. 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.