├── .gitignore ├── .github ├── FUNDING.yml ├── workflows │ ├── ci.yml │ └── publish.yml └── dependabot.yml ├── tsconfig.json ├── src └── index.tsx ├── LICENSE ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: sergiodxa 2 | github: sergiodxa 3 | custom: https://paypal.me/sergiodxa 4 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Use Node.js 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | - name: Install 17 | run: yarn install 18 | - name: Test 19 | run: yarn test 20 | env: 21 | CI: true 22 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | registry-url: https://registry.npmjs.org/ 16 | - run: yarn install 17 | - run: npm publish --access public 18 | env: 19 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "importHelpers": true, 7 | "declaration": true, 8 | "sourceMap": true, 9 | "rootDir": "./src", 10 | "strict": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const useSSRLayoutEffect = 4 | typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect; 5 | 6 | export function useIsMounted() { 7 | const isMounted = React.useRef(false); 8 | useSSRLayoutEffect(() => { 9 | isMounted.current = true; 10 | return () => { 11 | isMounted.current = false; 12 | }; 13 | }, []); 14 | return isMounted; 15 | } 16 | 17 | export default function useSafeCallback< 18 | Arguments extends Array = any[], 19 | ReturnValue = any 20 | >(callback: (...args: Arguments) => ReturnValue) { 21 | const isMounted = useIsMounted(); 22 | return React.useCallback( 23 | (...args: Arguments) => (isMounted.current ? callback(...args) : void 0), 24 | [callback] 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "10:00" 8 | open-pull-requests-limit: 10 9 | ignore: 10 | - dependency-name: y18n 11 | versions: 12 | - 4.0.1 13 | - 4.0.2 14 | - dependency-name: husky 15 | versions: 16 | - 4.3.8 17 | - 5.0.9 18 | - 5.1.0 19 | - 5.1.1 20 | - 5.1.2 21 | - 5.1.3 22 | - 5.2.0 23 | - dependency-name: "@types/react-dom" 24 | versions: 25 | - 17.0.0 26 | - 17.0.1 27 | - 17.0.2 28 | - dependency-name: "@types/react" 29 | versions: 30 | - 17.0.0 31 | - 17.0.1 32 | - 17.0.2 33 | - 17.0.3 34 | - dependency-name: typescript 35 | versions: 36 | - 4.1.3 37 | - 4.1.4 38 | - 4.1.5 39 | - 4.2.2 40 | - 4.2.3 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sergio Xalambrí 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-safe-callback", 3 | "author": "Sergio Xalambrí", 4 | "version": "1.0.2", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "sergiodxa/use-safe-callback" 9 | }, 10 | "main": "dist/index.js", 11 | "module": "dist/use-safe-callback.esm.js", 12 | "typings": "dist/index.d.ts", 13 | "files": [ 14 | "dist", 15 | "src" 16 | ], 17 | "engines": { 18 | "node": ">=10" 19 | }, 20 | "scripts": { 21 | "start": "tsdx watch", 22 | "build": "tsdx build", 23 | "test": "tsdx test --passWithNoTests", 24 | "lint": "tsdx lint", 25 | "prepare": "tsdx build" 26 | }, 27 | "peerDependencies": { 28 | "react": ">=16" 29 | }, 30 | "husky": { 31 | "hooks": { 32 | "pre-commit": "tsdx lint" 33 | } 34 | }, 35 | "prettier": { 36 | "printWidth": 80, 37 | "semi": true, 38 | "singleQuote": true, 39 | "trailingComma": "es5" 40 | }, 41 | "devDependencies": { 42 | "@types/react": "^17.0.20", 43 | "@types/react-dom": "^17.0.9", 44 | "husky": "^7.0.2", 45 | "react": "^16.14.0", 46 | "react-dom": "^16.14.0", 47 | "tsdx": "^0.13.2", 48 | "tslib": "^2.3.0", 49 | "typescript": "^4.4.3" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # useSafeCallback ![CI](https://github.com/sergiodxa/use-safe-callback/workflows/CI/badge.svg) ![Publish](https://github.com/sergiodxa/use-safe-callback/workflows/Publish/badge.svg) 2 | 3 | Wrap a function to ensure you never call it if a component is unmounted, useful to avoid cases where an async code could finish after a component has unmounted and it tries to update a state. 4 | 5 | ## Usage 6 | 7 | Install it 8 | 9 | ```sh 10 | $ yarn add use-safe-callback 11 | ``` 12 | 13 | Import it 14 | 15 | ```ts 16 | import useSafeCallback from "use-safe-callback" 17 | ``` 18 | 19 | Wrap your unsafe callback 20 | 21 | ```ts 22 | const [state, unsafeDispatch] = React.useReducer(reducer, initialState); 23 | const safeDispatch = useSafeCallback(unsafeDispatch); 24 | ``` 25 | 26 | Use it as your unsafe callback 27 | 28 | ```ts 29 | safeDispatch({ type: "MY_ACTION" }) 30 | ``` 31 | 32 | Your unsafe callback will not run if the component is unmounted, preventing you to trying to update the state. 33 | 34 | ## Author 35 | 36 | - [Sergio Xalambrí](https://sergiodxa.com) 37 | 38 | Based on a [similar Hook](https://github.com/tannerlinsley/react-query/blob/2c49b5d29ccd9204bd7e30b23efd08224b0f7560/src/react/utils.js#L45-L57) by [Tanner Linsley](https://twitter.com/tannerlinsley). 39 | 40 | ## License 41 | 42 | The MIT License. 43 | --------------------------------------------------------------------------------