tezvyn:

useAnimatedGestureHandler: UI-Thread Gesture Worklets

AI-drafted, machine-checkedintermediate

useAnimatedGestureHandler bridges RNGH events to Reanimated shared values on the UI thread via worklets, keeping pan gestures at 60fps without JS drops. The footgun is reading JS state inside worklet, which crashes because worklets run in an isolated runtime.

WHY IT EXISTS: React Native's gesture and animation pipelines used to live on opposite sides of the JS-to-native bridge. A gesture would fire a native event, cross to JavaScript, update state, and cross back to native to render. At 60 frames per second, that round trip created dropped frames whenever the JS thread was busy. Reanimated introduced a UI-thread runtime where animations could update without touching JavaScript. useAnimatedGestureHandler was built to plug react-native-gesture-handler events directly into that UI runtime, removing the bridge from the hot path.

THE MENTAL MODEL: Think of it as a direct wire from the user's finger to the pixel. Instead of the gesture handler shouting "the finger moved" across a crowded room to JavaScript, and JavaScript shouting back "move the view," the hook lets the gesture handler whisper directly to a tiny program running on the UI thread. That program mutates SharedValues, and those values drive the view's position every single frame.

HOW IT WORKS: You call the hook and pass an object with worklet callbacks, typically onStart, onActive, and onEnd. The hook returns a handler that you attach to a PanGestureHandler or similar component via its onGestureEvent prop. When the user touches down, onStart fires once and receives a mutable context object where you can stash the starting values of your SharedValues. As the finger moves, onActive fires every frame with the gesture event data, letting you compute new values from translationX, translationY, velocity, or scale and assign them directly to SharedValues. Because these callbacks run on the UI thread, there is no asynchronous gap between reading the touch and writing the view's transform.

WHEN TO USE IT: Reach for this hook when you need gesture-driven motion that stays locked to the user's touch at 60fps. Classic cases include bottom sheets that drag with the finger, swipeable list items, custom sliders, and pinch-to-zoom image viewers. It shines most when the JS thread is likely under load from lists, navigation transitions, or heavy computation, since the animation does not compete for JS time.

WHEN NOT TO USE IT: Do not use it if your gesture logic needs to read React state, props, or call ordinary JavaScript functions mid-gesture. Worklets run in an isolated UI runtime and cannot see the JS thread heap. If you need to validate a gesture against server data or complex application state, handle that in onEnd and ferry the result back to JS, or use the newer Gesture API from react-native-gesture-handler 2.x. Also avoid it for simple tap highlighting that native gesture handlers can drive without Reanimated.

ONE CANONICAL EXAMPLE: Imagine a card that slides horizontally with the finger. You create a SharedValue called translateX starting at zero. In useAnimatedGestureHandler, onStart stores the current translateX.value in context.startX. In onActive, you set translateX.value to context.startX plus event.translationX. A separate useAnimatedStyle reads translateX.value and applies it to the view's transform. The result is a card that follows the finger with sub-frame latency, even while the JS thread is blocked by a heavy list render.

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.