Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

use-long-press

Package Overview
Dependencies
Maintainers
1
Versions
52
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

use-long-press

React hook for detecting click, tap or point and hold event. Easy to use, highly customizable options, thoroughly tested.

  • 3.1.4-alpha.1
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
28K
decreased by-14.89%
Maintainers
1
Weekly downloads
 
Created
Source

React Long Press Hook

codecov npm type definitions npm bundle size npm

Logo horizontal

React hook for detecting click / tap / point and hold event

Main features

  • Mouse, Touch and Pointer events support
  • Pass custom context and access it in callback
  • Cancel long press if moved too far from the target
  • Flexible callbacks: onStart, onMove, onFinish, onCancel
  • Disable hook when necessary
  • Filter undesired events (like mouse right clicks)

Table of Contents

  1. Installation
  2. Basic Usage
  3. Advanced Usage
    1. Definition
    2. Callback
    3. Options
    4. Additional callbacks
    5. Result
    6. Context
    7. Handlers
  4. Examples
    1. Advanced usage example
    2. Live Examples
      1. Version 1
      2. Version 2
      3. Version 3
  5. Migration
    1. v1 to v2
    2. v2 to v3
    3. Long press outside element
  6. Changelog
  7. FAQ
  8. Support us
  9. License

Installation

yarn add use-long-press

or

npm install --save use-long-press

Basic Usage

import React from 'react';
import { useLongPress } from 'use-long-press';

const Example = () => {
  const bind = useLongPress(() => {
    console.log('Long pressed!');
  });

  return <button {...bind()}>Press me</button>;
};

Advanced usage

Hook definition

Pseudocode

useLongPress(callback [, options]): bindFn

TypeScript

declare function useLongPress<
  Target extends Element = Element,
  Context = unknown,
  Callback extends LongPressCallback<Target, Context> = LongPressCallback<Target, Context>
>(
  callback: Callback | null,
  options?: LongPressOptions<Target, Context>
): LongPressResult<LongPressHandlers<Target>, Context>;

Callback

Hook first parameter, callback, can be either function or null (if you want to disable the hook).

Options

You can supply options object as a hook second parameter. All options inside the object are optional.

NameTypeDefaultDescription
thresholdnumber400Time user need to hold click or tap before long press callback is triggered
captureEventbooleanfalseIf React MouseEvent (or TouchEvent) should be supplied as first argument to callbacks
detect'mouse' | 'touch' | 'pointer''pointer'Which event handlers should be returned from bind function.

TS enum: LongPressEventType
cancelOnMovementboolean | numberfalseIf long press should be cancelled when detected movement while pressing. Use boolean value to turn it on / off or number value to specify move tolerance in pixels.

For more information on how this option work check JSDoc.
cancelOutsideElementbooleantrueIf long press should be canceled when moving mouse / touch / pointer outside the element to which it was bound.

When cancelled returns LongPressCallbackReason.CancelledOutsideElement ('cancelled-outside-element') as a cancel reason in onCancel callback.

Works for mouse and pointer events, touch events will be supported in the future.
filterEvents(event) => booleanundefinedIf provided, it gives you the ability to ignore long press detection on specified conditions (e.g. on right mouse click).

When function returns false, it will prevent ANY callbacks from triggering (including onStart and onCancel) as well as capturing event.
onStart(event, meta) => voidundefinedCalled when element is initially pressed (before starting timer which detects long press)
onMove(event, meta) => voidundefinedCalled on move after pressing element. Since position is extracted from event after this callback is called, you can potentially make changes to event position.

Position is extracted using getCurrentPosition method from use-long-press.utils.ts
onFinish(event, meta) => voidundefinedCalled when press is released AFTER threshold time elapses, therefore after long press occurs and callback is called.
onCancel(event, meta) => voidundefinedCalled when press is released BEFORE threshold time elapses, therefore before long press could occur.

Additional callbacks

All callbacks (including main callback function) has same structure.

Pseudocode

callbackFn(event, meta): void

TypeScript

type LongPressCallback<Target extends Element = Element, Context = unknown> = (
  event: LongPressEvent<Target>,
  meta: LongPressCallbackMeta<Context>
) => void

As a first argument callback receives event from proper handler (e.g. onMouseDown) and as second receives meta object with following structure:

Pseudocode

{ [context: any], [reason: string] }

TypeScript

export type LongPressCallbackMeta<Context = unknown> = { context?: Context; reason?: LongPressCallbackReason };

Both object properties are optional.

  • context will be present if you pass it to bind function. See context for more info.
  • reason will be present in onCancel callback to indicate why long press was cancelled
    • 'cancelled-by-movement' (TS: LongPressCallbackReason.CancelledByMovement) - when cancelOnMovement option is enabled and moved outside specified tolerance
    • 'cancelled-by-release' (TS: LongPressCallbackReason.CancelledByRelease) - when press was released before threshold time elapsed

Result

As a result hook returns callable function (also referred as bind) in order to pass context if necessary. bind function return object with various handlers.

Context

You can supply custom context to the bind function like bind(context) and then access it from callbacks (onStart, callback, onFinish, onCancel, onMove) second argument e.g.: onStart: (event, { context }) => ....

Context changes during component lifecycle

All callbacks but long press callback will use latest provided context. callback will receive context as it was when long press started.

To better understand how component updates affect context let's analyze example below

  1. Initial render
import { useLongPress } from "./use-long-press";

const MyComponent: FC = () => {
  const bind = useLongPress();

  return <div {...bind('context 1')}/>;
}
  1. Div is clicked, triggering onStart with 'context 1'
  2. Context changed to 'context 2'
// ...
<div {...bind('context 2')}/>
// ...
  1. threshold time elapses triggering callback with 'context 1' as it was when long press started
  2. Click finish triggering onFinish with 'context 2' since it changed

Context value when long press finish outside element

When cancelOutsideElement option is set to false long press finish will be detected on window therefore it won't be possible to determine which context was used.

Let's say we use ...bind('test'), therefore onStart and callback will receive context 'test' but onCancel or onFinish if triggered outside element will receive context undefined

Handlers

Handlers are returned from bind function in a form of object which can be spread to react element. Contents of this object depend on detect option value:

  • 'mouse'
    • onMouseDown
    • onMouseMove
    • onMouseUp
    • onMouseLeave (only when cancelOutsideElement is enabled)
  • 'touch'
    • onTouchStart
    • onTouchMove
    • onTouchEnd
  • 'pointer'
    • onPointerDown
    • onPointerMove
    • onPointerUp
    • onPointerLeave (only when cancelOutsideElement is enabled)

Examples

Advanced usage example

import React, { useState, useCallback } from 'react';
import { useLongPress } from 'use-long-press';

export default function AdvancedExample() {
  const [enabled, setEnabled] = useState(true);
  const callback = useCallback(event => {
    alert('Long pressed!');
  }, []);
  const bind = useLongPress(enabled ? callback : null, {
    onStart: event => console.log('Press started'),
    onFinish: event => console.log('Long press finished'),
    onCancel: event => console.log('Press cancelled'),
    onMove: event => console.log('Detected mouse or touch movement'),
    filterEvents: event => true, // All events can potentially trigger long press (same as 'undefined')
    threshold: 500, // In milliseconds
    captureEvent: true, // Event won't get cleared after React finish processing it
    cancelOnMovement: 25, // Square side size (in pixels) inside which movement won't cancel long press
    cancelOutsideElement: true, // Cancel long press when moved mouse / pointer outside element while pressing
    detect: 'pointer', // Default option
  });

  return (
    <div>
      <button {...bind()}>Press and hold</button>
      <div>
        <label htmlFor="enabled">
          <input type="checkbox" id="enabled" checked={enabled} onChange={() => setEnabled(current => !current)} />
          Hook enabled
        </label>
      </div>
    </div>
  );
}

Live Examples

Version 1 (deprecated)

Edit useLongPress

Version 2 (deprecated)

Edit useLongPress

Version 3

Edit useLongPress

Migration

v1 to v2

[BREAKING CHANGE] Context support

Now hook returns function which can be called with any context in order to access it in callbacks

Before

const bind = useLongPress(() => console.log('Long pressed'));
// ...
return <button {...bind}>Click me</button>;

After

const bind = useLongPress((event, { context }) => console.log('Long pressed with', context));
// ...
return <button {...bind('I am context')}>Click me</button>;
// Or just empty function call if you don't want to pass any context
return <button {...bind()}>Click me</button>;

[NEW] Reason for cancellation

Now onCancel receives cancellation context which can be either:

  • LongPressEventReason.CANCELED_BY_TIMEOUT ('canceled-by-timeout')
  • LongPressEventReason.CANCELED_BY_MOVEMENT ('canceled-by-movement')

You can access it like this:

const bind = useLongPress(() => console.log('Long pressed'), {
  onCancel: (event, { reason }) => console.log('Cancellation reason:', reason) 
})

v2 to v3

[BREAKING CHANGE] Drop support for 'both' option in detect param

Returning both mouse and touch handlers as a hook result caused unintended edge cases on touch devices that emulated clicks. Therefore 'both' value was removed and hook is now using 'pointer' as a default value for detect param.

This also enables to support more type of events in the future.

Pointer events should be sufficient replacement for 'both' option, but you can also programmatically detect if current device support touch events and set proper detect value based on that.

Before

const bind = useLongPress(() => console.log('Long pressed'), {
  detect: 'both',
})

After

const bind = useLongPress(() => console.log('Long pressed'), {
  detect: 'pointer',
})

[BREAKING CHANGE] Typings and param values

TypeScript's typings were refactored to use more consistent and precise names. Also changed callback reason values (see LongPressEventReason)

  • Changed generics order from useLongPress<Target, Callback, Context> to useLongPress<Target, Context, Callback>
  • Renamed LongPressDetectEvents enum to LongPressEventType
    • LongPressDetectEvents.MOUSE -> LongPressEventType.Mouse
    • LongPressDetectEvents.TOUCH -> LongPressEventType.Touch
  • Added LongPressEventType.Pointer
  • Renamed LongPressEventReason enum to LongPressCallbackReason
    • LongPressEventReason.CANCELED_BY_MOVEMENT ('canceled-by-movement') -> LongPressCallbackReason.CancelledByMovement ('cancelled-by-movement')
    • LongPressEventReason.CANCELED_BY_TIMEOUT ('canceled-by-timeout') -> LongPressCallbackReason.CancelledByRelease ('cancelled-by-release')
  • Removed Coordinates type
  • Renamed EmptyObject type to LongPressEmptyHandlers
  • Renamed CallableContextResult type to LongPressResult
  • Renamed LongPressResult type to LongPressHandlers
  • Added mouse and touch handlers types - LongPressMouseHandlers and LongPressTouchHandlers

⚠️ Mouse / pointer leaving element while pressing

Versions before 3.1.0 did not have cancelOutsideElement option which when enabled make useLongPress behave in the same manner as v1 and v2 which is cancelling long press when mouse / pointer leave pressed element. Therefore, when added in 3.1.0 its default value was set to true, to restore previous versions behaviour.

Backstory behind it is that v3 was supposed to fix problem with detecting cancelling long press when finishing it outside component scope. It was achieved by detecting mouse / pointer up events on window and removing triggering cancel on mouse / pointer leaving the element. Unfortunately that solution was backward incompatible so adding cancelOutsideElement option was a way to fix that as well as new feature, hence why it was introduced as a minor version bump instead of a bugfix.

❗️It is recommended that you upgrade use-long-press to at least 3.1.0 in order to seamlessly migrate from v1 / v2 to v3❗️

Changelog

List of changes made with each version can be found here

FAQ

Why deprecate v1 and v2 and move to new repo?

v1 and v2 deprecation

Using both mouse and touch handlers on same element was a good idea at the beginning to enable out of the box support for all device types without the need to manually control which events should be detected. After adding support for pointer events that is no longer necessary because they are better suited to handle this case.

All tests had to be rewritten because while supporting React 17 and 18, using Enzyme for tests was no longer possible due to the lack of official adapters. Therefore, every test was rewritten to react-testing-library and generalised in order to be able to test each type of events (mouse, touch and pointer) without repeating the same code all over again.

Overall considering the reasons mentioned above, maintaining old versions was no longer a viable option hence why the deprecation. If you want to upgrade from v1 or v2 see migration guide.

Moving to new repository

Old repository structure was causing false positives on package vulnerabilities because of building / testing tools in dev dependencies. New monorepo architecture solves that problem by separating repository package.json from use-long-press package.json

Using monorepo is much easier for maintaining multiple packages and I plan to move use-double-tap and react-interval-hook to this repository as well as add new packages in the future.

Support us

If you like my work, consider making a donation through Github Sponsors.

License

MIT © minwork

Keywords

FAQs

Package last updated on 29 May 2023

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc