Skip to content

Instantly share code, notes, and snippets.

@getify
Last active September 29, 2021 11:58
Show Gist options
  • Star 44 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save getify/6bb0163f3c2768171389142f627cfb08 to your computer and use it in GitHub Desktop.
Save getify/6bb0163f3c2768171389142f627cfb08 to your computer and use it in GitHub Desktop.
experiment: mimicking React's new "useState()" hook for stand-alone functions, including "custom hooks"
"use strict";
[foo,bar] = TNG(foo,bar);
// NOTE: intentionally not TNG(..) wrapping useBaz(), so that it's
// basically like a "custom hook" that can be called only from other
// TNG-wrapped functions
function foo(origX,origY) {
var [x,setX] = useState(origX);
var [y,setY] = useState(origY);
useBaz("foo"); // calling useBaz(..) like it's a "custom hook"
console.log(`foo: ${x} ${y}`);
setX( x * 2 );
setY( bar(y) );
return origX;
}
function bar(curY) {
var [z,setZ] = useState(curY + 1);
useBaz("bar"); // calling useBaz(..) like it's a "custom hook"
console.log(`bar: ${z}`);
z = z * curY;
setZ( z );
return z;
}
// since useBaz(..) isn't TNG-wrapped, it's like a "custom hook", in that
// you don't call it stand-alone, but rather only call it from another
// TNG-wrapped function. thus, useBaz(..) uses the context of the calling
// TNG-wrapped function for its useState(..) calls.
function useBaz(which) {
var [count,setCount] = useState(0);
count++;
setCount(count);
console.log(`${which} count: ${count}`);
}
foo(3,9);
// foo count: 1
// foo: 3 9
// bar count: 1
// bar: 10
foo();
// foo count: 2
// foo: 6 90
// bar count: 2
// bar: 90
bar(42);
// bar count: 3 <--- only updated the `count` state of bar(..)
// bar: 8100
foo();
// foo count: 3 <--- `count` state is separate between foo(..) and bar(..)
// foo: 12 8100
// bar count: 4 <--- ditto
// bar: 340200
var { TNG, useState } = (function def(){
"use strict";
var buckets = new WeakMap();
var currentBucket = [];
return { TNG, useState };
// ******************
function TNG(...fns) {
fns = fns.map(function mapper(fn){
return function tngf(...args) {
if (buckets.has(tngf)) {
let bucket = buckets.get(tngf);
bucket.nextIdx = 0;
}
currentBucket.push(tngf);
try {
return fn.apply(this,args);
}
finally {
currentBucket.pop();
}
};
});
if (fns.length < 2) return fns[0];
return fns;
}
function useState(defVal) {
if (currentBucket.length > 0) {
let tngf = currentBucket[currentBucket.length - 1];
let bucket;
if (!buckets.has(tngf)) {
bucket = { nextIdx: 0, slots: [], };
buckets.set(tngf,bucket);
}
bucket = buckets.get(tngf);
if (!(bucket.nextIdx in bucket.slots)) {
let slot = [
typeof defVal == "function" ? defVal() : defVal,
function updateSlot(v){ slot[0] = v; }
];
bucket.slots[bucket.nextIdx] = slot;
}
return [...bucket.slots[bucket.nextIdx++]];
}
else {
throw new Error("Only use useState() inside of (or functions called from) TNG-wrapped functions.");
}
}
})();
@getify
Copy link
Author

getify commented Nov 14, 2018

I've published this experiment as an actual project/package, called "TNG-Hooks":

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment