Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
use-long-press
Advanced tools
React hook for detecting click, tap or point and hold event. Easy to use, highly customizable options, thoroughly tested.
React hook for detecting click, tap or point and hold event
onStart
, onMove
, onFinish
, onCancel
yarn add use-long-press
or
npm install --save use-long-press
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>;
};
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>;
Hook first parameter, callback, can be either function or null
(if you want to disable the hook).
You can supply options object as a hook second parameter. All options inside the object are optional.
Name | Type | Default | Description |
---|---|---|---|
threshold | number | 400 | Time user need to hold click or tap before long press callback is triggered |
captureEvent | boolean | false | If 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 |
cancelOnMovement | boolean | number | false | If 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. |
filterEvents | (event) => boolean | undefined | If 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) => void | undefined | Called when element is initially pressed (before starting timer which detects long press) |
onMove | (event, meta) => void | undefined | Called 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) => void | undefined | Called when press is released AFTER threshold time elapses, therefore after long press occurs and callback is called. |
onCancel | (event, meta) => void | undefined | Called when press is released BEFORE threshold time elapses, therefore before long press could occur. |
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 elapsedAs a result hook returns callable function (also referred as bind
) in order to pass context if necessary.
bind
function return object with various handlers.
You can supply custom context to the bind
function like bind(context)
and then access it from callbacks (onStart
, onFinish
, onCancel
, onMove
) second argument e.g.: onStart: (event, { context }) => ...
.
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
'touch'
onTouchStart
onTouchMove
onTouchEnd
'pointer'
onPointerDown
onPointerMove
onPointerUp
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
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>
);
}
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>;
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)
})
'both'
option in detect
paramReturning 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',
})
TypeScript's typings were refactored to use more consistent and precise names. Also changed callback reason values (see LongPressEventReason
)
useLongPress<Target, Callback, Context>
to useLongPress<Target, Context, Callback>
LongPressDetectEvents
enum to LongPressEventType
LongPressDetectEvents.MOUSE
-> LongPressEventType.Mouse
LongPressDetectEvents.TOUCH
-> LongPressEventType.Touch
LongPressEventType.Pointer
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')Coordinates
typeEmptyObject
type to LongPressEmptyHandlers
CallableContextResult
type to LongPressResult
LongPressResult
type to LongPressHandlers
LongPressMouseHandlers
and LongPressTouchHandlers
List of changes made with each version can be found here
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.
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.
If you like my work, consider making a donation through Github Sponsors.
MIT © minwork
FAQs
React hook for detecting click, tap or point and hold event. Easy to use, highly customizable options, thoroughly tested.
The npm package use-long-press receives a total of 22,724 weekly downloads. As such, use-long-press popularity was classified as popular.
We found that use-long-press demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
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.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.