├── .gitignore ├── README.md ├── index.d.ts ├── index.js ├── index.ts ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # Docz 107 | .docz -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # usePureCallback 2 | 3 | **This is not a NPM package, this is simple snipet to copy paste it.** 4 | 5 | [React docs reference](https://reactjs.org/docs/hooks-faq.html#how-to-read-an-often-changing-value-from-usecallback). 6 | 7 | Just copy the code below and paste it to your project. 8 | 9 | > You can find `.ts` / `.d.ts` / `js` version of this code in the root of [the repo](https://github.com/artalar/usePureCallback). 10 | 11 | ```ts 12 | import { useRef } from "react"; 13 | 14 | /** 15 | * Return a stable link to the decorated function, 16 | * which accept dynamic dependencies and other needed arguments 17 | * @example 18 | * // `handleChange` will always same, even if `props.onChange` changing. 19 | * const handleChange = usePureCallback( 20 | * [props.onChange], 21 | * ([onChange], event) => onChange(event.target.value) 22 | * ) 23 | * @see {@link github.com/artalar/usePureCallback} 24 | */ 25 | export function usePureCallback( 26 | deps: Deps, 27 | fn: (deps: Deps, ...args: Args) => Return 28 | ): (...args: Args) => Return { 29 | const argsRef = useRef({ 30 | fn: (...args: Args) => fn(argsRef.current.deps, ...args), 31 | deps 32 | }); 33 | // `useLayoutEffect` for React <18 34 | useInsertionEffect(() => { 35 | argsRef.current.deps = deps; 36 | }) 37 | return argsRef.current.fn; 38 | } 39 | ``` 40 | 41 | ## Motivation 42 | 43 | You could memoize any kind of data, by its strict / shallow / deep / custom comparison except a function. If the function depends on some dynamic outer data, it should be recreated by each data changing and even if the function is used rarely, it will break all below memoization, which we could call a **parasite immutability**. 44 | To prevent this harmful behavior, we should separate 'dirty' data and function logic to mutable reference and pure function, which depend only on its arguments. The funny thing - we could do it super easy, check the code above. 45 | 46 | ## Limitations 47 | 48 | In the rare cases you need to react to the function changing (do a side effect), which looks like antipattern already, but if you still need this behavior, use native `useCallback`. 49 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Return a stable link to the decorated function, 3 | * which accept dynamic dependencies and other needed arguments 4 | * @example 5 | * // `handleChange` will always same, even if `props.onChange` changing. 6 | * const handleChange = usePureCallback( 7 | * [props.onChange], 8 | * ([onChange], event) => onChange(event.target.value) 9 | * ) 10 | * @see {@link github.com/artalar/usePureCallback} 11 | */ 12 | export declare function usePureCallback(deps: Deps, fn: (deps: Deps, ...args: Args) => Return): (...args: Args) => Return; 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | /** 3 | * Return a stable link to the decorated function, 4 | * which accept dynamic dependencies and other needed arguments 5 | * @example 6 | * // `handleChange` will always same, even if `props.onChange` changing. 7 | * const handleChange = usePureCallback( 8 | * [props.onChange], 9 | * ([onChange], event) => onChange(event.target.value) 10 | * ) 11 | * @see {@link github.com/artalar/usePureCallback} 12 | */ 13 | export function usePureCallback(deps, fn) { 14 | const argsRef = useRef({ 15 | fn: (...args) => fn(argsRef.current.deps, ...args), 16 | deps 17 | }); 18 | argsRef.current.deps = deps; 19 | return argsRef.current.fn; 20 | } 21 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | import { useRef } from "react"; 2 | 3 | /** 4 | * Return a stable link to the decorated function, 5 | * which accept dynamic dependencies and other needed arguments 6 | * @example 7 | * // `handleChange` will always same, even if `props.onChange` changing. 8 | * const handleChange = usePureCallback( 9 | * [props.onChange], 10 | * ([onChange], event) => onChange(event.target.value) 11 | * ) 12 | * @see {@link github.com/artalar/usePureCallback} 13 | */ 14 | export function usePureCallback( 15 | deps: Deps, 16 | fn: (deps: Deps, ...args: Args) => Return 17 | ): (...args: Args) => Return { 18 | const argsRef = useRef({ 19 | fn: (...args: Args) => fn(argsRef.current.deps, ...args), 20 | deps 21 | }); 22 | argsRef.current.deps = deps; 23 | return argsRef.current.fn; 24 | } 25 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usepurecallback", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "usepurecallback", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "typescript": "^4.5.4" 13 | }, 14 | "peerDependencies": { 15 | "react": "^16.8.0" 16 | } 17 | }, 18 | "node_modules/js-tokens": { 19 | "version": "4.0.0", 20 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 21 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 22 | "peer": true 23 | }, 24 | "node_modules/loose-envify": { 25 | "version": "1.4.0", 26 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 27 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 28 | "peer": true, 29 | "dependencies": { 30 | "js-tokens": "^3.0.0 || ^4.0.0" 31 | }, 32 | "bin": { 33 | "loose-envify": "cli.js" 34 | } 35 | }, 36 | "node_modules/object-assign": { 37 | "version": "4.1.1", 38 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 39 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 40 | "peer": true, 41 | "engines": { 42 | "node": ">=0.10.0" 43 | } 44 | }, 45 | "node_modules/prop-types": { 46 | "version": "15.8.1", 47 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 48 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 49 | "peer": true, 50 | "dependencies": { 51 | "loose-envify": "^1.4.0", 52 | "object-assign": "^4.1.1", 53 | "react-is": "^16.13.1" 54 | } 55 | }, 56 | "node_modules/react": { 57 | "version": "16.14.0", 58 | "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", 59 | "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", 60 | "peer": true, 61 | "dependencies": { 62 | "loose-envify": "^1.1.0", 63 | "object-assign": "^4.1.1", 64 | "prop-types": "^15.6.2" 65 | }, 66 | "engines": { 67 | "node": ">=0.10.0" 68 | } 69 | }, 70 | "node_modules/react-is": { 71 | "version": "16.13.1", 72 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 73 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 74 | "peer": true 75 | }, 76 | "node_modules/typescript": { 77 | "version": "4.5.4", 78 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", 79 | "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", 80 | "dev": true, 81 | "bin": { 82 | "tsc": "bin/tsc", 83 | "tsserver": "bin/tsserver" 84 | }, 85 | "engines": { 86 | "node": ">=4.2.0" 87 | } 88 | } 89 | }, 90 | "dependencies": { 91 | "js-tokens": { 92 | "version": "4.0.0", 93 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 94 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 95 | "peer": true 96 | }, 97 | "loose-envify": { 98 | "version": "1.4.0", 99 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", 100 | "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", 101 | "peer": true, 102 | "requires": { 103 | "js-tokens": "^3.0.0 || ^4.0.0" 104 | } 105 | }, 106 | "object-assign": { 107 | "version": "4.1.1", 108 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 109 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 110 | "peer": true 111 | }, 112 | "prop-types": { 113 | "version": "15.8.1", 114 | "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", 115 | "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", 116 | "peer": true, 117 | "requires": { 118 | "loose-envify": "^1.4.0", 119 | "object-assign": "^4.1.1", 120 | "react-is": "^16.13.1" 121 | } 122 | }, 123 | "react": { 124 | "version": "16.14.0", 125 | "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", 126 | "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", 127 | "peer": true, 128 | "requires": { 129 | "loose-envify": "^1.1.0", 130 | "object-assign": "^4.1.1", 131 | "prop-types": "^15.6.2" 132 | } 133 | }, 134 | "react-is": { 135 | "version": "16.13.1", 136 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", 137 | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", 138 | "peer": true 139 | }, 140 | "typescript": { 141 | "version": "4.5.4", 142 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", 143 | "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", 144 | "dev": true 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usepurecallback", 3 | "version": "1.0.0", 4 | "description": "useCallback doing right", 5 | "main": "index.js", 6 | "typings": "index.d.ts", 7 | "scripts": { 8 | "build": "tsc --target esnext --moduleResolution node --declaration index.ts" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/artalar/usePureCallback.git" 13 | }, 14 | "keywords": [ 15 | "react", 16 | "useCallback" 17 | ], 18 | "author": "artalar", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/artalar/usePureCallback/issues" 22 | }, 23 | "homepage": "https://github.com/artalar/usePureCallback#readme", 24 | "peerDependencies": { 25 | "react": "^16.8.0" 26 | }, 27 | "devDependencies": { 28 | "typescript": "^4.5.4" 29 | } 30 | } 31 | --------------------------------------------------------------------------------