npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

reversible-effect

v2.0.1

Published

A collection of typed utility functions returning a callback to reverse their effect.

Downloads

226

Readme

reversible effect ↩️

Cleanup Utilities for JavaScript Side-Effects

A zero dependency collection of typed utility functions returning a callback to reverse their effect.

This package includes reversible implementations of:

Hey you! Yes, you!
Are you tired of writing this?

useEffect(() => {
	const myCallback = () => {
		console.log('window resized');
	};
	window.addEventListener('resize', myCallback);
	return () => {
		window.removeEventListener('resize', myCallback);
	};
}, []);

How about writing this instead?

useEffect(
    () => addReversibleEventListener(window, 'resize', () => {
        console.log('window resized');
    }), []
);

Let's find out how or why.

Content

Installation

using npm:

npm install reversible-effect

using yarn:

yarn add reversible-effect

Just import the required function from the package, here's an example for addReversibleEventListener:

import { addReversibleEventListener } from 'reversible-effect';

See below for available functions and how to use them.

Motivation

For a detailed explanation, check out this Medium article, but here's the gist of it:

Functions like setTimeout or addEventListener come with an accompanying function to reverse their effect, like clearTimeout or removeEventListener.

To be able to use them, you need to keep track of which effect you're trying to reverse, so you can call the appropriate function. To make matters worse, these functions require different parameters.

You also need to keep track of all references to identify what it is you want to cancel (a timeoutId for clearTimeOut or the eventName as well as the callback for removeEventListener).

This package and the functions it provides aim to improve this:

  • Unify the interface:
    Doesn't matter what type of effect – just call function to reverse it.
  • Simplify the implementation:
    No need to keep track of timeout IDs or callback references.
  • Provide type security:
    When using typescript the respective function parameters should mirror the behaviour of their originals.

Documentation

Basic Usage

Every function in this package returns a callback, which serves as its cleanup.

This allows you to do this:

// create an interval
const cancelInterval = setReversibleInterval(() => console.log('called'), 1000);
// cancel it
cancelInterval();

Note that you don't have to keep track of a timeoutID, you can pass around a reference to the cleanup function to wherever you need it.
You can then call it without needing to know that it is an interval you're cancelling.
It would work just the same for an event:

// add an event listener
const remove = addReversibleEventListener(window, 'click', () => console.log('clicked'));
// remove it
remove();

React's useEffect

A common use case of these functions is within a react's useEffect hook, which conveniently expects you to return function to reverse the effect.
As every function returns its own cleanup, you can return the reversible function from the hook directly.

This example will add the event listener, whenever the component mounts and remove it, when it unmounts:

useEffect(() => addReversibleEventListener(window, 'click', e => console.log(e.clientX, e.clientY)), []);

Here we start an interval, when the component mounts and stop it, when it unmounts:

useEffect(() => setReversibleInterval(() => setCount(count => count + 1), 500), []);

Note: If you add dependencies to the useEffect hook here, the timer will reset any time a dependency changes.
See here to learn why and how to get around this.

If you have multiple effects, you could either define multiple useEffect hooks or use them like this:

useEffect(() => {
	const cancelTimeout = setReversibleTimeout(() => {}, 1000);
	const removeEventListener = addEventListener(window, 'click', () => {});
	return () => {
		cancelTimeout();
		removeEventListener();
	};
}, []);

Available functions

addReversibleEventListener

Reversible version of object.addEventListener. → docs for original

This function has two overloads, which switch based on the provided target. If target is an object we know the supported event types for, typescript will both limit type to supported types and provide the correct event type to the callback.

function addReversibleEventListener(
	target: EventTargetWithKnownEvents,
	type: AvailableEventsForThisTarget, // string literal of available event listeners
	listener: (e: SpecificEvent) => void, // Callback with Specific Event based on type
	options?: boolean | AddEventListenerOptions
): () => void;

If we can't determine the event type, it will fall back to a generic version:

function addReversibleEventListener(
	target: GenericTarget,
	type: string,
	listener: (e: Event) => void,
	options?: boolean | AddEventListenerOptions
): () => void;

Note that there is shared behaviour with addEventListener/removeEventListener when called with the same arguments:

  1. When adding the same listener multiple times, it will not be added more than once.
  2. A returned cleanup can cancel any listener that was added using the same arguments.

See below examples for details:

const callback = () => console.log('hello');
const cancel1 = addReversibleEventListener('click', callback);
cancel1();
const cancel2 = addReversibleEventListener('click', callback);
const cancel3 = addReversibleEventListener('click', callback);
//              ⬆ Since the passed arguments are the same, the callback will only be called once, when the event is fired.
cancel1();
// ⬆ Even though it has been used before this will remove the listener. It is functionally equivalent to `cancel2` and `cancel3`.

If you specifically want to add a listener multiple times or make sure a cleanup function only concerns the listener it created, the solution is the same as with the originals – make the callback is referentially unique:

const cancel1 = addReversibleEventListener('click', e => callback(e));
const cancel2 = addReversibleEventListener('click', e => callback(e));
//              ⬆ The passed parameter is referentially unique, so the callback is executed twice, when the event is fired.
cancel1();
// ⬆ This will only ever remove the first listener, the second still fires, until `cancel2()` is called.

setReversibleTimeout

Reversible version of [window.]setTimeout. → docs for original

function setReversibleTimeout(
	callback: (...args: any[]) => void, // function to be executed after `delay`
	delay?: number = 0, // delay for timeout
	...args: any[] // optional args to be passed into `callback`
): () => void;

setReversibleInterval

Reversible version of [window.]setInterval. → docs for original

function setReversibleInterval(
	callback: (...args: any[]) => void, // function to be executed every `delay`
	delay?: number = 0, // delay between executions
	...args: any[] // optional args to be passed into `callback`
): () => void;

requestReversibleAnimationFrame

Reversible version of window.requestAnimationFrame. → docs for original

function requestReversibleAnimationFrame(
	callback: (time: DOMHighResTimeStamp): void; // function to be executed on next repaint
): () => void;

Contributing

Improvements or additions are most welcome!

Current list of planned support:

This package uses npm as its package manager.

Fork / Clone the repo, run npm install, then npm start to build in watch mode.

Run npm run test:watch to run the tests, don't forget to add new tests, if you add functionality.

Create a PR, describing your change.

Support

Found a bug or need help? Add a new issue.