├── .all-contributorsrc ├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── .eslintrc └── index.test.js ├── package-lock.json ├── package.json ├── src └── index.js └── types ├── index.d.ts ├── tsconfig.json └── use-event-listener-tests.tsx /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "use-event-listener", 3 | "projectOwner": "donavon", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": false, 11 | "contributors": [ 12 | { 13 | "login": "donavon", 14 | "name": "Donavon West", 15 | "avatar_url": "https://avatars3.githubusercontent.com/u/887639?v=4", 16 | "profile": "http://donavon.com", 17 | "contributions": [ 18 | "infra", 19 | "test", 20 | "example", 21 | "ideas", 22 | "maintenance", 23 | "review", 24 | "tool", 25 | "code" 26 | ] 27 | }, 28 | { 29 | "login": "third774", 30 | "name": "Kevin Kipp", 31 | "avatar_url": "https://avatars3.githubusercontent.com/u/8732191?v=4", 32 | "profile": "https://github.com/third774", 33 | "contributions": [ 34 | "code" 35 | ] 36 | }, 37 | { 38 | "login": "wKovacs64", 39 | "name": "Justin Hall", 40 | "avatar_url": "https://avatars1.githubusercontent.com/u/1288694?v=4", 41 | "profile": "https://github.com/wKovacs64", 42 | "contributions": [ 43 | "code", 44 | "doc" 45 | ] 46 | }, 47 | { 48 | "login": "huan086", 49 | "name": "Jeow Li Huan", 50 | "avatar_url": "https://avatars2.githubusercontent.com/u/1448788?v=4", 51 | "profile": "https://github.com/huan086", 52 | "contributions": [ 53 | "review" 54 | ] 55 | }, 56 | { 57 | "login": "normanrz", 58 | "name": "Norman Rzepka", 59 | "avatar_url": "https://avatars1.githubusercontent.com/u/335438?v=4", 60 | "profile": "http://normanrz.com/", 61 | "contributions": [ 62 | "ideas" 63 | ] 64 | }, 65 | { 66 | "login": "bvanderdrift", 67 | "name": "Beer van der Drift", 68 | "avatar_url": "https://avatars1.githubusercontent.com/u/6398452?v=4", 69 | "profile": "https://github.com/bvanderdrift", 70 | "contributions": [ 71 | "test", 72 | "code" 73 | ] 74 | }, 75 | { 76 | "login": "pruge", 77 | "name": "clingsoft", 78 | "avatar_url": "https://avatars1.githubusercontent.com/u/5827473?v=4", 79 | "profile": "https://github.com/pruge", 80 | "contributions": [ 81 | "code" 82 | ] 83 | } 84 | ], 85 | "contributorsPerLine": 7, 86 | "skipCi": true 87 | } 88 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | indent_size = 2 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "amex", 3 | "rules": { 4 | "consistent-return": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | .DS_Store 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | 58 | # dotenv environment variables file 59 | .env 60 | 61 | # next.js build output 62 | .next 63 | 64 | # lib 65 | lib/ 66 | dist/ 67 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src 3 | .editorconfig 4 | .eslintrc 5 | .babelrc 6 | __tests__ 7 | coverage 8 | .prettierrc 9 | .travis.yml 10 | .all-contributorsrc 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5" 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "14" 5 | 6 | jobs: 7 | include: 8 | - stage: test 9 | script: npm t 10 | - stage: npm release 11 | if: branch = master 12 | deploy: 13 | provider: npm 14 | email: $NPM_EMAIL 15 | api_key: $NPM_TOKEN 16 | skip_cleanup: true 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-present Donavon West 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @use-it/event-listener 2 | 3 | A custom React Hook that provides a declarative useEventListener. 4 | 5 | [](https://badge.fury.io/js/%40use-it%2Fevent-listener) [](#contributors) 6 | 7 | This hook was inspired by [Dan Abramov](https://github.com/gaearon)'s 8 | blog post 9 | ["Making setInterval Declarative with React Hooks"](https://overreacted.io/making-setinterval-declarative-with-react-hooks/). 10 | 11 | I needed a way to simplify the plumbing around adding and removing an event listener 12 | in a custom hook. 13 | That lead to a [chain of tweets](https://twitter.com/donavon/status/1093612936621379584) 14 | between Dan and myself. 15 | 16 | ## Installation 17 | 18 | ```bash 19 | $ npm i @use-it/event-listener 20 | ``` 21 | 22 | or 23 | 24 | ```bash 25 | $ yarn add @use-it/event-listener 26 | ``` 27 | 28 | ## Usage 29 | 30 | Here is a basic setup. 31 | 32 | ```js 33 | useEventListener(eventName, handler, element, options); 34 | ``` 35 | 36 | ### Parameters 37 | 38 | Here are the parameters that you can use. (\* = optional) 39 | 40 | | Parameter | Description | 41 | | :---------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 42 | | `eventName` | The event name (string). Here is a list of [common events](https://developer.mozilla.org/en-US/docs/Web/Events). | 43 | | `handler` | A function that will be called whenever `eventName` fires on `element`. | 44 | | `element`\* | An optional element to listen on. Defaults to `global` (i.e., `window`). | 45 | | `options`\* | An object `{ capture?: boolean, passive?: boolean, once?: boolean }` to be passed to `addEventListener`. For advanced use cases. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener) for details. | 46 | 47 | ### Return 48 | 49 | This hook returns nothing. 50 | 51 | ## Example 52 | 53 | Let's look at some sample code. Suppose you would like to track the mouse 54 | position. You _could_ subscribe to mouse move events with like this. 55 | 56 | ```js 57 | const useMouseMove = () => { 58 | const [coords, setCoords] = useState([0, 0]); 59 | 60 | useEffect(() => { 61 | const handler = ({ clientX, clientY }) => { 62 | setCoords([clientX, clientY]); 63 | }; 64 | window.addEventListener('mousemove', handler); 65 | return () => { 66 | window.removeEventListener('mousemove', handler); 67 | }; 68 | }, []); 69 | 70 | return coords; 71 | }; 72 | ``` 73 | 74 | Here we're using `useEffect` to roll our own handler add/remove event listener. 75 | 76 | `useEventListener` abstracts this away. You only need to care about the event name 77 | and the handler function. 78 | 79 | ```js 80 | const useMouseMove = () => { 81 | const [coords, setCoords] = useState([0, 0]); 82 | 83 | useEventListener('mousemove', ({ clientX, clientY }) => { 84 | setCoords([clientX, clientY]); 85 | }); 86 | 87 | return coords; 88 | }; 89 | ``` 90 | 91 | ## Live demo 92 | 93 | You can view/edit the sample code above on CodeSandbox. 94 | 95 | [](https://codesandbox.io/s/k38lyx2q9o) 96 | 97 | ## License 98 | 99 | **[MIT](LICENSE)** Licensed 100 | 101 | ## Contributors 102 | 103 | Thanks goes to these wonderful people ([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)): 104 | 105 | 106 | 107 | 108 |
Donavon West 🚇 ⚠️ 💡 🤔 🚧 👀 🔧 💻 |
111 | Kevin Kipp 💻 |
112 | Justin Hall 💻 📖 |
113 | Jeow Li Huan 👀 |
114 | Norman Rzepka 🤔 |
115 | Beer van der Drift ⚠️ 💻 |
116 | clingsoft 💻 |
117 |