$ npm install bind-event-listener
A utility to make binding and (especially) unbinding DOM events easier. I seem to write this again with every new project, so I made it a library
import { bind, UnbindFn } from 'bind-event-listener';
const unbind: UnbindFn = bind(button, {
type: 'click',
listener: onClick,
});
// when you are all done:
unbind();
import { bindAll } from 'bind-event-listener';
const unbind = bind(button, [
{
type: 'click',
listener: onClick,
options: { capture: true },
},
{
type: 'mouseover',
listener: onMouseOver,
},
]);
// when you are all done:
unbind();
When using addEventListener()
, correctly unbinding events with removeEventListener()
can be tricky.
removeEventListener
(it can be easy to forget!)target.addEventListener('click', onClick, options);
// You need to remember to call removeEventListener to unbind the event
target.removeEventListener('click', onClick, options);
removeEventListener
target.addEventListener(
'click',
function onClick() {
console.log('clicked');
},
options,
);
// Even those the functions look the same, they don't have the same reference.
// The original onClick is not unbound!
target.removeEventListener(
'click',
function onClick() {
console.log('clicked');
},
options,
);
// Inline arrow functions can never be unbound because you have lost the reference!
target.addEventListener('click', () => console.log('i will never unbind'), options);
target.removeEventListener('click', () => console.log('i will never unbind'), options);
capture
value option// add a listener: AddEventListenerOptions format
target.addEventListener('click', onClick, { capture: true });
// not unbound: no capture value
target.removeEventListener('click', onClick);
// not unbound: different capture value
target.removeEventListener('click', onClick, { capture: false });
// successfully unbound: same capture value
target.removeEventListener('click', onClick, { capture: true });
// this would also unbind (different notation)
target.removeEventListener('click', onClick, true /* shorthand for { capture: true } */);
// add a listener: boolean capture format
target.addEventListener('click', onClick, true /* shorthand for { capture: true } */);
// not unbound: no capture value
target.addEventListener('click', onClick);
// not unbound: different capture value
target.addEventListener('click', onClick, false);
// successfully unbound: same capture value
target.addEventListener('click', onClick, true);
// this would also unbind (different notation)
target.addEventListener('click', onClick, { capture: true });
bind-event-listener
solves these problems
bindAll
) you get back a simple unbind
functionremoveEventListener
capture
value is used with addEventListener
is used with removeEventListener
bind
: basicimport { bind, UnbindFn } from 'bind-event-listener';
const unbind: UnbindFn = bind(button, {
type: 'click',
listener: onClick,
});
// when your are all done:
unbind();
bind
: with optionsimport { bind } from 'bind-event-listener';
const unbind = bind(button, {
type: 'click',
listener: onClick,
options: { capture: true, passive: false },
});
// when you are all done:
unbind();
bindAll
: basicimport { bindAll } from 'bind-event-listener';
const unbind = bindAll(button, [
{
type: 'click',
listener: onClick,
},
]);
// when you are all done:
unbind();
bindAll
: with optionsimport { bindAll } from 'bind-event-listener';
const unbind = bindAll(button, [
{
type: 'click',
listener: onClick,
options: { passive: true },
},
// default options that are applied to all bindings
{ capture: false },
]);
// when you are all done:
unbind();
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:
const merged: AddEventListenerOptions = {
...defaultOptions,
...options,
};
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 } }
react
effectYou 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.
import React, { useState, useEffect } from 'react';
import { bind } from 'bind-event-listener';
export default function App() {
const [clickCount, onClick] = useState(0);
useEffect(() => {
const unbind = bind(window, {
type: 'click',
listener: () => onClick((value) => value + 1),
});
return unbind;
}, []);
return <div>Window clicks: {clickCount}</div>;
}
You can play with this example on codesandbox
function bind(target: EventTarget, binding: Binding): UnbindFn;
function bindAll(
target: EventTarget,
bindings: Binding[],
// AddEventListenerOptions is a built in TypeScript type
sharedOptions?: boolean | AddEventListenerOptions,
): UnbindFn;
type Binding = {
type: string;
// EventListenerOrEventListenerObject is a built in TypeScript type
listener: EventListenerOrEventListenerObject;
options?: boolean | AddEventListenerOptions;
};
type UnbindFn = () => void;
Typescript built in DOM types: raw view, pretty view (warning: pretty view seems to crash Github!)
Brought to you by @alexandereardon
© 2010 - cnpmjs.org x YWFE | Home | YWFE