├── .gitignore ├── .npmignore ├── .npmrc ├── .travis.yml ├── LICENSE ├── README.md ├── async.js ├── cjs ├── async.js ├── context.js ├── e.js ├── effect.js ├── extras.js ├── hooks.js ├── index.js ├── memo.js ├── package.json ├── reducer.js └── ref.js ├── e.js ├── es.js ├── esm.js ├── esm ├── async.js ├── context.js ├── e.js ├── effect.js ├── extras.js ├── hooks.js ├── index.js ├── memo.js ├── reducer.js └── ref.js ├── index.js ├── package.json ├── rollup ├── async.config.js ├── es.config.js ├── esm.config.js └── index.config.js ├── test ├── async.html ├── context.html ├── context.js ├── e.js ├── index.js ├── multi.html ├── package.json └── timer.js └── uhooks.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | node_modules/ 4 | coverage/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .nyc_output 3 | .travis.yml 4 | node_modules/ 5 | rollup/ 6 | test/ 7 | coverage/ 8 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | git: 5 | depth: 1 6 | branches: 7 | only: 8 | - main 9 | after_success: 10 | - "npm run coveralls" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # µhooks 2 | 3 | [![Build Status](https://travis-ci.com/WebReflection/uhooks.svg?branch=main)](https://travis-ci.com/WebReflection/uhooks) [![Coverage Status](https://coveralls.io/repos/github/WebReflection/uhooks/badge.svg?branch=main)](https://coveralls.io/github/WebReflection/uhooks?branch=main) [![CSP strict](https://webreflection.github.io/csp/strict.svg)](https://webreflection.github.io/csp/#-csp-strict) 4 | 5 | ![hooks](./uhooks.jpg) 6 | 7 | **Social Media Photo by [Tatiana Rodriguez](https://unsplash.com/@tata186) on [Unsplash](https://unsplash.com/)** 8 | 9 | ### 📣 Community Announcement 10 | 11 | Please ask questions in the [dedicated discussions repository](https://github.com/WebReflection/discussions), to help the community around this project grow ♥ 12 | 13 | --- 14 | 15 | _micro hooks_ is a simplified _~0.8K_ alternative to [augmentor](https://github.com/WebReflection/augmentor#readme), with the following differences: 16 | 17 | * `hooked(fn)` is the *augmentor* entry point equivalent 18 | * multiple states update are applied at once asynchronously (these are a *Promise.then(...)* away) 19 | * `useEffect` is also applied asynchronously 20 | * there are no extra options whatsoever so it's less configurable 21 | * there is no `contextual` export, as every hook can have a context passed along, whenever it's needed, or a good idea at all 22 | * exports from `uhooks/async` allows `hooked(async () => { ... })` definitions 23 | * the [uhooks/e](./esm/e.js) export provides an *essential* utility with `useState` and `useRef`, usable in micro-controllers or whenever synchronous, simplified, hooks are enough, and code size/memory constraints are relevant. 24 | 25 | The reason for this module to exist is to explore a slightly different pattern that is *not* stack-based, but that should perform overall better in real-world use cases, thanks to its smaller size and its reduced amount of invokes applied in bulks. 26 | 27 | ```js 28 | // 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/context.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | uhooks 7 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /test/context.js: -------------------------------------------------------------------------------- 1 | const { 2 | hooked, 3 | createContext, useContext, 4 | } = require('../cjs'); 5 | 6 | const ctx = createContext(0); 7 | 8 | const log = () => { 9 | console.log(useContext(ctx)); 10 | }; 11 | 12 | const log1 = hooked(log); 13 | 14 | const log2 = hooked(log); 15 | 16 | log1(); 17 | log2(); 18 | 19 | setTimeout(() => { 20 | ctx.provide(1); 21 | }); 22 | -------------------------------------------------------------------------------- /test/e.js: -------------------------------------------------------------------------------- 1 | const {hooked, useState, useRef} = require('../cjs/e'); 2 | 3 | const context = new Set; 4 | const args = new Set; 5 | const driver = {}; 6 | 7 | const test = hooked(function () { 8 | context.add(this); 9 | args.add(arguments); 10 | const [state1, update1] = useState(1); 11 | const [state2, update2] = useState(2); 12 | const ref1 = useRef(1); 13 | const ref2 = useRef(2); 14 | driver.states = [ 15 | [state1, update1], 16 | [state2, update2] 17 | ]; 18 | driver.refs = [ 19 | ref1, 20 | ref2 21 | ]; 22 | }); 23 | 24 | test.call(Object, 1, 2, 3); 25 | 26 | console.assert([...context][0] === Object, 'unexpected context'); 27 | console.assert([].slice.call([...args][0]).join(',') === '1,2,3', 'unexpected arguments'); 28 | 29 | console.assert(driver.states[0][0] === 1); 30 | console.assert(driver.states[1][0] === 2); 31 | console.assert(driver.refs[0].current === 1); 32 | console.assert(driver.refs[1].current === 2); 33 | 34 | driver.refs[1].current = 3; 35 | driver.states[1][1](3); 36 | 37 | console.assert(driver.states[0][0] === 1); 38 | console.assert(driver.states[1][0] === 3); 39 | console.assert(driver.refs[0].current === 1); 40 | console.assert(driver.refs[1].current === 3); 41 | 42 | console.assert(context.size === 1); 43 | console.assert(args.size === 2); 44 | 45 | driver.refs[0].current = 2; 46 | driver.states[0][1](2); 47 | 48 | console.assert(driver.states[0][0] === 2); 49 | console.assert(driver.states[1][0] === 3); 50 | console.assert(driver.refs[0].current === 2); 51 | console.assert(driver.refs[1].current === 3); 52 | 53 | console.assert(context.size === 1); 54 | console.assert(args.size === 3); 55 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const { 2 | hooked, 3 | hasEffect, dropEffect, 4 | createContext, useContext, 5 | useCallback, useMemo, 6 | useEffect, useLayoutEffect, 7 | useReducer, useState, 8 | useRef 9 | } = require('../cjs'); 10 | 11 | const Counter = (start) => { 12 | const [count, setCount] = useState(start); 13 | const {current} = useRef({}); 14 | current.increment = () => { 15 | setCount(count + 1); 16 | }; 17 | console.log(count); 18 | return current; 19 | }; 20 | 21 | const comp = hooked(Counter)(1); 22 | comp.increment(); 23 | 24 | (async () => { 25 | 26 | let test = function () { 27 | console.assert(arguments.length === 1 && arguments[0] === 123); 28 | console.assert(this == 1); 29 | }; 30 | hooked(test).call(1, 123); 31 | 32 | test = function () { 33 | const [value, update] = useState(0); 34 | if (value === 0) 35 | update(value + 1); 36 | else 37 | console.assert(value === 1); 38 | }; 39 | hooked(test)(); 40 | 41 | test = function () { 42 | const [value, update] = useState(() => 0); 43 | if (value === 0) 44 | update(() => value + 2); 45 | else 46 | console.assert(value === 2); 47 | }; 48 | hooked(test)(); 49 | 50 | test = function () { 51 | const [value, update] = useReducer((_, curr) => curr, -1, () => 0); 52 | const [other, refresh] = useReducer((_, curr) => curr, -1, () => 0); 53 | if (value === 0) { 54 | update(value + 1); 55 | refresh(other + 2); 56 | } 57 | else { 58 | console.assert(value === 1); 59 | console.assert(other === 2); 60 | } 61 | }; 62 | hooked(test)(); 63 | 64 | test = function () { 65 | const ref = useRef(null); 66 | const [value, update] = useState(ref.current); 67 | if (value === null) 68 | update(ref.current = 123); 69 | else 70 | console.assert(ref.current === 123); 71 | }; 72 | hooked(test)(); 73 | 74 | test = hooked(function () { 75 | const [value, update] = useState(() => 0); 76 | let i = 0, y = 1; 77 | useLayoutEffect(() => { 78 | console.assert(i === 0); 79 | }); 80 | useEffect(() => { 81 | return () => { 82 | y = 2; 83 | }; 84 | }); 85 | if (value === 0) 86 | update(1); 87 | }); 88 | test(); 89 | test(); 90 | 91 | let i = 0; 92 | test = hooked(function () { 93 | const [value, update] = useState(() => 0); 94 | useEffect(() => { 95 | console.assert(i++ === 0); 96 | return () => { console.log('dropEffect'); }; 97 | }, []); 98 | if (value === 0) 99 | update(1); 100 | }); 101 | test(); 102 | console.assert(hasEffect(test)); 103 | dropEffect(test); 104 | 105 | test = hooked(function () { 106 | const [count, setCount] = useState(0); 107 | const handleCount = useCallback(() => setCount(count + 1), [count]); 108 | if (count === 0) 109 | handleCount(); 110 | }); 111 | test(); 112 | dropEffect(test); 113 | 114 | test = function () { 115 | const [count, setCount] = useState(0); 116 | const handleCount = useCallback(() => setCount(count + 1)); 117 | if (count === 0) 118 | handleCount(); 119 | }; 120 | hooked(test)(); 121 | 122 | let ctx = createContext(1); 123 | test = function () { 124 | const value = useContext(ctx); 125 | useEffect(() => { 126 | if (value === 1) { 127 | ctx.provide(value + 1); 128 | ctx.provide(value + 1); 129 | } 130 | }, [value]); 131 | }; 132 | hooked(test)(); 133 | 134 | test = function () { 135 | const [count, setCount] = useState(0); 136 | const current = useMemo(() => count, [count]); 137 | const future = useMemo(() => count, []); 138 | if (current === future) 139 | setCount(current + 1); 140 | }; 141 | hooked(test)(); 142 | })(); 143 | -------------------------------------------------------------------------------- /test/multi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | {"type":"commonjs"} -------------------------------------------------------------------------------- /test/timer.js: -------------------------------------------------------------------------------- 1 | const {hooked, useState, useEffect} = require('../cjs'); 2 | 3 | const Counter = hooked(name => { 4 | const [count, setCount] = useState(0); 5 | 6 | useEffect(() => { 7 | const time = setTimeout(setCount, 1000, count + 1); 8 | return () => clearTimeout(time); 9 | }, [count]); 10 | 11 | console.log(`${name}: ${count}`); 12 | }); 13 | 14 | Counter('Hit'); 15 | -------------------------------------------------------------------------------- /uhooks.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebReflection/uhooks/105eab5b8cc318840095c19c6dd587f0e6433d06/uhooks.jpg --------------------------------------------------------------------------------