Get started in 3 steps
Your app must render a GestureHandlerRootView above the component. The installation page has the full setup.
Install
Add Impulse, gesture-handler, and Reanimated.
yarn add @rootnative/impulse react-native-gesture-handlerImport
One intent hook, plus the detector that renders it.
import { useTap } from '@rootnative/impulse'Detect
Name what the gesture means. Impulse owns the mechanism.
const tap = useTap({ onTap: select })Why Impulse
RNGH is a good library, and it is not the problem. It exposes mechanisms on purpose. Impulse clears the sharp edges that every app otherwise rebuilds.
Intents, not mechanisms
One hook per intent. RNGH exposes activation criteria, relations, and state machines and leaves the intent to you. Impulse names the intent and keeps the mechanism as the escape hatch.
Composition is data
useGestures([tap, double, pan], { mode: "race" }) reads left to right, and a composed result is itself composable. No inside-out Gesture.Simultaneous(Gesture.Race(...)) nesting.
Three named relations
alongside runs together, blocks wins over, deferTo waits for the other to fail. Each maps to exactly one RNGH relation, so coexisting with a scroll view stops being a guess.
Stable gesture identity
A JS-thread callback is never a gesture dependency, so an inline callback cannot re-attach the detector mid-drag. A test pins identity across an inline-callback re-render.
The callback names its thread
onBegin, onUpdate, and onFinalize are worklets. onTap, onDragEnd, and onLongPress run on JS, and Impulse owns the scheduleOnRN boundary. You write no runOnJS.
Payloads shaped per intent
A drag hands back { translation, velocity, absolute, settled }. The flat union of translationX, focalX, and numberOfPointers is never the public type.
The same drag, twice
A horizontal drag that must let a vertical scroll view win. In RNGH that is a builder chain, a pair of shared values, and the start.value dance. In Impulse drag.x accumulates across gestures, and deferTo is the relation.
import { useDrag } from '@rootnative/impulse'
export function SwipeRow() {
const drag = useDrag({
axis: 'x',
deferTo: scrollRef,
onDragEnd: (e) => commit(e.translation.x > 100),
})
return (
<GestureDetector gesture={drag.gesture}>
<Row x={drag.x} />
</GestureDetector>
)
}Impulse detects. It does not animate.
Every hook returns shared values and a gesture. Impulse starts no animation, owns no transition vocabulary, and ships no components. Pass the values to the animation library you already use.
Impulse
Detects the gesture. Hands back shared values, a payload shaped for the intent, and the gesture itself.
Inertia
Animates. @rootnative/inertia is the other half of the pair. Impulse will never require an animation library.
Ready to detect?
Pick the intent, hand it a callback, and wrap the view in a GestureDetector.