Skip to content

Instantly share code, notes, and snippets.

@WebReflection
Created January 4, 2021 17:50
Show Gist options
  • Save WebReflection/77b005e5693cf5e5c1fa1ea781b673d8 to your computer and use it in GitHub Desktop.
Save WebReflection/77b005e5693cf5e5c1fa1ea781b673d8 to your computer and use it in GitHub Desktop.

Revisions

  1. WebReflection created this gist Jan 4, 2021.
    103 changes: 103 additions & 0 deletions hooks-in-a-nutshell.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    // WARNING: There's much more to know/do around hooks, and
    // this is just a simplification of how these work.

    // shared references, updated
    // per each hook invoke
    let execution = null;
    let current = null;
    let context = null;
    let args = null;

    // returns a function that updates known
    // references each time it gets executed
    // so that using useState within such function
    // can retrieve all details related to the hook
    const hooked = callback => {

    // create a stack to incrementally retrieve
    // or add new states whenever it's needed
    const details = {i: 0, stack: []};

    // return the hook function that resets index,
    // update shared references, and then executes
    return function hook() {

    // reset the stack index
    details.i = 0;

    // update shared references
    execution = details;
    current = hook;
    context = this;
    args = arguments;

    // invoke the original callback
    return callback.apply(context, args);
    };
    };

    // every time a hook invokes a useState
    // the initial value is either known or new
    const useState = value => {

    // trap in this closure current details
    // so that it's possible to re-invoke the hook
    // with latest provided context and arguments
    const hook = current;
    const ctx = context;
    const rest = args;

    // grab current stack and its index
    const {i, stack} = execution;

    // increment the stack index so that the next
    // useState(value) will get its slot
    execution.i++;

    // if the stack does not know about this slot
    if (i === stack.length) {
    // store the initial value in the stack,
    // making the current stack[i] reachable
    stack.push(value);
    }

    return [

    // the last value stored at this index
    // or the one we've just pushed
    stack[i],

    // the update function that re-invoke the hook
    value => {

    // update this stack value with a new one
    stack[i] = value;

    // execute the hook one more time,
    // so that everything gets synchronized
    hook.apply(ctx, rest);
    // this will repeat this recursive dance
    // until no state update happens
    }
    ];
    };

    // EXAMPLE - Counter factory

    // the hooked callback can be defined a part
    // as long as it's invoked only once hooked
    const toBeWrapped = start => {
    const [num, update] = useState(start);
    console.log(`Counting: ${num}`);
    setTimeout(update, 1000, num + 1);
    };

    // in this case the Counter returns, each time,
    // a new hooked version of the same callback
    // and it executes it right away
    function Counter() {
    return hooked(toBeWrapped).apply(this, arguments);
    }

    // read a counting from 0 to N in console
    Counter(0);