How does JavaScript mechanism behind react hooks work?

The state value has to be stored outside of the useState function, in some internal representation of the component instance, so that it returns persistent results across calls. Additionally setting the value has to cause a rerender on the component it gets called in:

// useState must have a reference to the component it was called in:
let context;

function useState(defaultValue) {
  // Calling useState outside of a component won't work as it needs the context:
  if (!context) {
    throw new Error("Can only be called inside render");
  }

  // Only initialize the context if it wasn't rendered yet (otherwise it would re set the value on a rerender)
  if (!context.value) {
    context.value = defaultValue;
  }

  // Memoize the context to be accessed in setValue
  let memoizedContext = context;

  function setValue(val) {
    memoizedContext.value = val;

    // Rerender, so that calling useState will return the new value
    internalRender(memoizedContext);
  }

  return [context.value, setValue];
}

// A very simplified React mounting logic:
function internalRender(component) {
  context = component;
  component.render();
  context = null;
}

// A very simplified component
var component = {
  render() {
    const [value, update] = useState("it");
    console.log(value);
    setTimeout(update, 1000, "works!");
  }
};

internalRender(component);

Then when setValue gets called, the component rerenders, useState will get called again, and the new value will get returned.

The upper example is very simplified. Here’s a few things that React does differently:

  1. The state is not stored in a “context property” but rather in a linked list. Whenever useState is called, the linked list advances to the next node. That’s why you should not use hooks in branches/loops.
  2. The setState function gets cached and the same reference gets returned each time.
  3. Rerendering does not happen synchronously.

Leave a Comment