A utility to make binding and (especially) unbinding DOM events easier
Installations
npm install bind-event-listener
Developer Guide
Typescript
Yes
Module System
CommonJS
Node Version
18.13.0
NPM Version
8.19.3
Releases
Contributors
Unable to fetch Contributors
Languages
TypeScript (98.87%)
JavaScript (1.13%)
Love this project? Help keep it running — sponsor us today! 🚀
Developer
alexreardon
Download Statistics
Total Downloads
14,143,701
Last Day
2,149
Last Week
2,149
Last Month
720,318
Last Year
8,088,800
GitHub Statistics
149 Stars
107 Commits
8 Forks
5 Watching
11 Branches
4 Contributors
Bundle Size
1.24 kB
Minified
554.00 B
Minified + Gzipped
Package Meta Information
Latest Version
3.0.0
Package Id
bind-event-listener@3.0.0
Unpacked Size
20.32 kB
Size
6.38 kB
File Count
15
NPM Version
8.19.3
Node Version
18.13.0
Publised On
26 Apr 2023
Total Downloads
Cumulative downloads
Total Downloads
14,143,701
Last day
0%
2,149
Compared to previous day
Last week
-98.6%
2,149
Compared to previous week
Last month
-6.4%
720,318
Compared to previous month
Last year
180.2%
8,088,800
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
bind-event-listener
A well typed utility to make creating and removing DOM event listeners safer and more ergonomic.
1import { bind, UnbindFn } from 'bind-event-listener'; 2 3const unbind: UnbindFn = bind(button, { 4 type: 'click', 5 listener: function onClick(event) {}, 6}); 7 8// when you are all done: 9unbind();
1import { bindAll } from 'bind-event-listener'; 2 3const unbind = bindAll(button, [ 4 { 5 type: 'click', 6 listener: function onClick(event) {}, 7 options: { capture: true }, 8 }, 9 { 10 type: 'mouseover', 11 listener: function onMouseOver(event) {}, 12 }, 13]); 14 15// when you are all done: 16unbind();
Rationale
When using addEventListener()
, correctly unbinding events with removeEventListener()
can be tricky.
- You need to remember to call
removeEventListener
(it can be easy to forget!)
Example
1target.addEventListener('click', onClick, options); 2 3target.removeEventListener('click', onClick, options);
- You need to pass in the same listener reference to
removeEventListener
Example
1target.addEventListener( 2 'click', 3 function onClick() { 4 console.log('clicked'); 5 }, 6 options, 7); 8 9// Even those the functions look the same, they don't have the same reference. 10// The original onClick is not unbound! 11target.removeEventListener( 12 'click', 13 function onClick() { 14 console.log('clicked'); 15 }, 16 options, 17);
1// Inline arrow functions can never be unbound because you have lost the reference! 2target.addEventListener('click', () => console.log('i will never unbind'), options); 3target.removeEventListener('click', () => console.log('i will never unbind'), options);
- You need to pass in the same
capture
value option
Example
1// add a listener: AddEventListenerOptions format 2target.addEventListener('click', onClick, { capture: true }); 3 4// not unbound: no capture value 5target.removeEventListener('click', onClick); 6 7// not unbound: different capture value 8target.removeEventListener('click', onClick, { capture: false }); 9 10// successfully unbound: same capture value 11target.removeEventListener('click', onClick, { capture: true }); 12// this would also unbind (different notation) 13target.removeEventListener('click', onClick, true /* shorthand for { capture: true } */);
1// add a listener: boolean capture format
2target.addEventListener('click', onClick, true /* shorthand for { capture: true } */);
3
4// not unbound: no capture value
5target.addEventListener('click', onClick);
6// not unbound: different capture value
7target.addEventListener('click', onClick, false);
8
9// successfully unbound: same capture value
10target.addEventListener('click', onClick, true);
11// this would also unbind (different notation)
12target.addEventListener('click', onClick, { capture: true });
bind-event-listener
solves these problems
- When you bind an event (or events with
bindAll
) you get back a simpleunbind
function - The unbind function ensures the same listener reference is passed to
removeEventListener
- The unbind function ensures that whatever
capture
value is used withaddEventListener
is used withremoveEventListener
You will find an even fuller rationale for this project in my course: "The Ultimate Guide for Understanding DOM Events"
Usage
bind
: basic
1import { bind, UnbindFn } from 'bind-event-listener'; 2 3const unbind: UnbindFn = bind(button, { 4 type: 'click', 5 listener: onClick, 6}); 7 8// when your are all done: 9unbind();
bind
: with options
1import { bind } from 'bind-event-listener'; 2 3const unbind = bind(button, { 4 type: 'click', 5 listener: onClick, 6 options: { capture: true, passive: false }, 7}); 8 9// when you are all done: 10unbind();
bindAll
: basic
1import { bindAll } from 'bind-event-listener'; 2 3const unbind = bindAll(button, [ 4 { 5 type: 'click', 6 listener: onClick, 7 }, 8]); 9 10// when you are all done: 11unbind();
bindAll
: with options
1import { bindAll } from 'bind-event-listener';
2
3const unbind = bindAll(button, [
4 {
5 type: 'click',
6 listener: onClick,
7 options: { passive: true },
8 },
9 // default options that are applied to all bindings
10 { capture: false },
11]);
12
13// when you are all done:
14unbind();
When using defaultOptions
for bindAll
, the defaultOptions
are merged with the options
on each binding. Options on the individual bindings will take precedent. You can think of it like this:
1const merged: AddEventListenerOptions = { 2 ...defaultOptions, 3 ...options, 4};
Note: it is a little bit more complicated than just object spreading as the library will also behave correctly when passing in a
boolean
capture argument. An options value can be a boolean{ options: true }
which is shorthand for{ options: {capture: true } }
Types
Thanks to the great work by @Ayub-Begimkulov and @Andarist bind-event-listener
has fantastic TypeScript types and auto complete.
⚠️ TypeScript 4.1+ is required for types ⚠️ TypeScript 5.0+ is required for event name autocompletion
1import invariant from 'tiny-invariant'; 2import { bind } from 'bind-event-listener'; 3 4bind(window, { 5 type: 'click', 6 function: function onClick(event) { 7 // `event` is correctly typed as a 'MouseEvent' 8 // `this` is correctly typed as `window` (the event target that the event listener is added to) 9 }, 10}); 11 12const button = document.querySelector('button'); 13invariant(button instanceof HTMLElement); 14 15bind(button, { 16 type: 'click', 17 function: function onClick(event) { 18 // `event` is correctly typed as a 'MouseEvent' 19 // `this` is correctly typed as `button` (the event target that the event listener is added to) 20 }, 21}); 22 23const object = { 24 handleEvent: function onClick(event) { 25 // `event` is correctly typed as a 'MouseEvent' 26 // `this` is correctly typed as `object` (the event listener object that the event listener is added to) 27 }, 28}; 29 30bind(button, { 31 type: 'click', 32 function: object, 33});
bind
and bindAll
accept type arguments (generics), but it is generally best to let these be inferred
1// with explicit type arguments 2bind<HTMLElement, 'click'>(button, { 3 type: 'click', 4 listener: function onClick() {}, 5}); 6 7// ✨ types will automatically be inferred for you ✨ 8bind(button, { 9 type: 'click', 10 listener: function onClick() {}, 11}); 12 13// with explicit type arguments 14bindAll<HTMLElement, ['click', 'keydown']>(button, [ 15 { 16 type: 'click', 17 listener: function onClick() {}, 18 }, 19 { 20 type: 'keydown', 21 listener: function onKeyDown() {}, 22 }, 23]); 24 25// ✨ types will automatically be inferred for you ✨ 26bindAll(button, [ 27 { 28 type: 'click', 29 listener: function onClick() {}, 30 }, 31 { 32 type: 'keydown', 33 listener: function onKeyDown() {}, 34 }, 35]);
Typescript built in DOM types: raw view, pretty view (warning: pretty view seems to crash Github!)
Type helpers
1import { Binding, Listener, UnbindFn } from 'bind-event-listener';
Listener
: the function
or object
that you provide to the listener
property of a Binding
1bind(button, { 2 type: 'click', 3 listener: function onClick() {}, // ← `Listener` 4});
Binding
: the definition of an event binding.
1bind( 2 button, 3 // ↓ `Binding` 4 { 5 type: 'click', 6 listener: function onClick() {}, 7 }, 8);
UnbindFn
: a named type for () => void
to make it clearer that the function will unbind the added event listener(s):
1const unbind: UnbindFn = bind(button, { type: 'click', listener: function onClick() {} });
Recipe: react
effect
You can return a cleanup function from useEffect
(or useLayoutEffect
). bind-event-listener
makes this super convenient because you can just return the unbind function from your effect.
1import React, { useState, useEffect } from 'react'; 2import { bind } from 'bind-event-listener'; 3 4export default function App() { 5 const [clickCount, onClick] = useState(0); 6 7 useEffect(() => { 8 const unbind = bind(window, { 9 type: 'click', 10 listener: () => onClick((value) => value + 1), 11 }); 12 13 return unbind; 14 }, []); 15 16 return <div>Window clicks: {clickCount}</div>; 17}
You can play with this example on codesandbox
Cheers 👋
Brought to you by @alexandereardon
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE:0
- Info: FSF or OSI recognized license: MIT License: LICENSE:0
Reason
Found 3/10 approved changesets -- score normalized to 3
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/bundle-size-check.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/bundle-size-check.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/bundle-size-check.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/bundle-size-check.yml/master?enable=pin
- Warn: third-party GitHubAction not pinned by hash: .github/workflows/bundle-size-check.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/bundle-size-check.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/test.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/test.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:28: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/test.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/validate.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/validate.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/validate.yml:20: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/validate.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/validate.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/alexreardon/bind-event-listener/validate.yml/master?enable=pin
- Info: 0 out of 8 GitHub-owned GitHubAction dependencies pinned
- Info: 0 out of 1 third-party GitHubAction dependencies pinned
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/bundle-size-check.yml:1
- Warn: no topLevel permission defined: .github/workflows/test.yml:1
- Warn: no topLevel permission defined: .github/workflows/validate.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 27 are checked with a SAST tool
Reason
10 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3
- Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55
- Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw
- Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3
- Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
Score
3.1
/10
Last Scanned on 2025-02-03
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn MoreOther packages similar to bind-event-listener
react-event-listener
A React mixin that enable components to bind events
discord.js-message-listener
A simple utility to bind events on discord message.
@indevstudio/react-event-listener
A React component that allow to bind events on the global scope
react-use-listener
attach native event without and don't care about bind / unbind