Skip to content

Instantly share code, notes, and snippets.

@aziis98
Last active August 14, 2024 13:06
Show Gist options
  • Save aziis98/c8de74a29d5b1721983501fe28eb25e2 to your computer and use it in GitHub Desktop.
Save aziis98/c8de74a29d5b1721983501fe28eb25e2 to your computer and use it in GitHub Desktop.
Lexical "hooks" in JS using TemplateStringsArray to distinguish callsites (in contrast to react hooks that use a form of stack/dynamic scoping)
//
// Library
//
const newValueToUpdater = newValue => (typeof newValue === 'function' ? newValue : () => newValue)
const hooks = {
state: (hookContext, initialValue) => {
if (!hookContext.initialized) {
hookContext.initialized = true
hookContext.state = initialValue
}
return [
hookContext.state,
newValue => {
hookContext.state = newValueToUpdater(newValue)(hookContext.state)
},
]
},
memo: (hookContext, fn, deps) => {
if (!hookContext.initialized) {
hookContext.initialized = true
hookContext.value = fn()
hookContext.deps = deps
}
if (deps.any(dep => hookContext.deps === undefined || hookContext.deps[dep] !== deps[dep])) {
hookContext.value = fn()
hookContext.deps = deps
}
return hookContext.value
},
effect: (hookContext, fn, deps) => {
if (!hookContext.initialized) {
// console.log('effect init', deps)
hookContext.initialized = true
hookContext.cleanup = fn()
hookContext.deps = deps
} else {
// console.log('effect update', hookContext.deps, deps)
if (deps.some((dep, i) => dep !== hookContext.deps[i])) {
hookContext.cleanup()
hookContext.cleanup = fn()
hookContext.deps = deps
}
}
},
}
const m = new WeakMap()
function use(strings) {
const key = strings[0]
const hook = hooks[key]
if (!hook) {
throw new Error(`Unknown hook: ${key}`)
}
return (...hookArgs) => {
let hookContext = m.get(strings)
if (hookContext === undefined) {
hookContext = {}
m.set(strings, hookContext)
}
return hook(hookContext, ...hookArgs)
}
}
//
// Example
//
function foo() {
const [value, setValue] = use`state`(-1)
console.log('foo state', value)
bar(setValue)
bar(setValue)
}
function bar(setValue) {
const [barValue, setBarValue] = use`state`(0)
console.log('bar state', barValue)
setValue(v => v * 2)
setBarValue(v => v + 1)
use`effect`(() => {
console.log('bar divisibility by 3 changed!', barValue)
return () => console.log('cleanup after', barValue)
}, [barValue % 3 === 0])
}
foo()
foo()
foo()
foo()
foo state -1
bar state 0
bar divisibility by 3 changed! 0
bar state 1
cleanup after 0
bar divisibility by 3 changed! 1
foo state -4
bar state 2
bar state 3
cleanup after 1
bar divisibility by 3 changed! 3
foo state -16
bar state 4
cleanup after 3
bar divisibility by 3 changed! 4
bar state 5
foo state -64
bar state 6
cleanup after 4
bar divisibility by 3 changed! 6
bar state 7
cleanup after 6
bar divisibility by 3 changed! 7
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment