skip to content

Gestures & Motion

Touches reach your code through the JS-thread responder system or Gesture Handler's native recognizers, and animations run through Animated or Reanimated. Interviewers probe which thread each uses.

part ofReact Nativeoverview, primer and where to startread it →
on this pageshow

explore

questions

28

In React Native, how do you animate a like-button heart pop with Animated.spring, and what does useNativeDriver: true change?

level: juniorimportance: must knowfreq 75%

answer

  1. a value, an animation, an Animated.View
  2. useAnimatedValue keeps one value
  3. timing, spring or decay
  4. the animation is sent to native up front
  5. useNativeDriver must be set explicitly

basics

~10 s

Keep an Animated.Value from useAnimatedValue, start Animated.spring on it, and bind it to an Animated.View's transform scale. useNativeDriver: true hands the animation to native before it starts, so the UI thread computes every frame.

solid answer

~40 s

You create a value with `useAnimatedValue(1)`, which keeps one `Animated.Value` across renders, and bind it to `<Animated.View style={{transform: [{scale}]}}>`. On a like, set it to a smaller scale and call `Animated.spring(scale, {toValue: 1, friction: 3, useNativeDriver: true}).start()` so the heart springs back with an overshoot. `Animated.timing` instead eases over a fixed `duration` (default 500 ms, ease-in-out), and `Animated.decay` slows an initial `velocity` to rest. `useNativeDriver: true` serializes the animation and hands it to native code before it starts; the UI thread then computes every frame, so a busy JavaScript thread, such as one rendering the feed, cannot make the pop stutter. The option is required: leaving it out logs a warning, fails TypeScript's check, and falls back to the JS driver. The native driver only accepts non-layout styles like `transform` and `opacity`.

code

tsx · 30 lines
tsx
import {Animated, Pressable, StyleSheet, useAnimatedValue} from 'react-native';
import {useState} from 'react';

export function LikeHeart() {
  const [liked, setLiked] = useState(false);
  const scale = useAnimatedValue(1);

  const onPress = () => {
    setLiked(prev => !prev);
    scale.setValue(0.6);
    Animated.spring(scale, {
      toValue: 1,
      friction: 3,
      tension: 120,
      useNativeDriver: true,
    }).start();
  };

  return (
    <Pressable onPress={onPress} accessibilityRole="button" accessibilityLabel="Like">
      <Animated.Text style={[styles.heart, {transform: [{scale}]}]}>
        {liked ? '\u2665' : '\u2661'}
      </Animated.Text>
    </Pressable>
  );
}

const styles = StyleSheet.create({
  heart: {fontSize: 32, color: '#e0245e'},
});

go deeper

for a junior

Recall the three pieces: an Animated.Value from useAnimatedValue, a timing, spring or decay animation, and an Animated component, plus what useNativeDriver does.

for a middle

Explain why the native driver survives a busy JS thread, the defaults of timing and spring, and why the option must be explicit.

for a senior

Show judgement about when native driving is possible, which styles force the JS driver, and how an animation that must stay smooth during list rendering is designed.

for a principal

Set a team default for animation work, Animated with the native driver versus a worklet-based library, and the review rules that keep janky JS-driven animations out.

## The three pieces of an Animated animation React Native's `Animated` API is built from three parts: 1. **A value.** `Animated.Value` holds a number that changes over time; `Animated.ValueXY` holds an `{x, y}` pair for two-dimensional motion. In a function component, `useAnimatedValue(initial)` (or `useAnimatedValueXY`) creates it once and returns the same instance on every render; `new Animated.Value()` inside the component body would create a fresh one each render. 2. **An animation.** A driver such as `Animated.timing`, `Animated.spring` or `Animated.decay` moves the value toward a target. Calling `.start()` runs it, optionally with a callback receiving `{finished}`. 3. **An animated component.** `Animated.View`, `Animated.Text`, `Animated.Image` and friends accept animated values inside `style`, for example `transform: [{scale}]`. ## Choosing a driver | Driver | What it models | Key options and defaults | |---|---|---| | `Animated.timing` | a curve over fixed time | `duration` 500 ms, `easing` ease-in-out, `delay` 0 | | `Animated.spring` | a physical spring that can overshoot | `friction` 7 and `tension` 40 by default, or `bounciness`/`speed`, or `stiffness`/`damping`/`mass`; only one family at a time | | `Animated.decay` | an initial velocity slowing to rest | `velocity` required, `deceleration` 0.998 | For a like-button heart, `spring` gives the playful overshoot: start the scale below 1 and spring to 1 with a low `friction`. Mixing families, for example `friction` with `stiffness`, fails an invariant. ## What useNativeDriver: true changes Without the native driver, JavaScript computes each frame and pushes the new value to the view. With it: - The animation is **serializable**, so React Native sends its full description to native code **before** starting it. - Native code on the **UI thread** computes every frame and applies it to the view. - Once started, the **JavaScript thread can be blocked** without affecting the animation, which matters when a like happens while the feed is rendering new posts. Three rules come with it: - **Set it explicitly.** The option is required; if it is missing, `Animated` logs a warning, TypeScript reports an error, and the JS driver is used. - **Non-layout styles only** by default: `transform`, `opacity`, colours and similar properties work; `width`, `height`, margins and positions do not. - **One driver per value.** Once a value has run a native-driven animation, running a JS-driven animation on it throws. ## Building the heart pop - Keep a `scale` value at `1`. - On press, `scale.setValue(0.6)` and start `Animated.spring(scale, {toValue: 1, friction: 3, tension: 120, useNativeDriver: true})`. - The low friction makes the spring overshoot past 1 and settle, which reads as a pop. - Toggle the liked state in React as usual; the animation is decoration, not the source of truth. ## Common mistakes - Creating `new Animated.Value(1)` in the component body, so every render resets the heart. - Omitting `useNativeDriver`, then wondering why the pop stutters while the list scrolls. - Trying to pop the heart by animating `width` and `height` with the native driver instead of `transform: [{scale}]`. - Using a plain `View` instead of `Animated.View`, so the animated value is never applied. ## Shaping the motion - **Easing for `timing`.** The default is `Easing.inOut(Easing.ease)`. `Easing.out(Easing.back())` overshoots slightly at the end, `Easing.bezier(x1, y1, x2, y2)` matches a design tool's curve, and `Easing.linear` suits progress bars. - **Delay.** `delay` in the config waits before the animation starts, useful for chaining small effects without a sequence. - **Spring feel.** Lower `friction` (or `damping`) gives more bounce; higher `tension` (or `stiffness`) makes it faster. Pick one parameter family and tune only that. - **Starting velocity.** Both `spring` and `decay` accept a `velocity`, so an animation that follows a gesture can continue its speed instead of starting from rest. - **Two dimensions.** `Animated.ValueXY` animates `{x, y}` together, and `getTranslateTransform()` turns it into a `translateX`/`translateY` transform. ## What the start callback tells you `start(({finished}) => ...)` runs when the animation ends. `finished` is `true` for a normal end and `false` when it was stopped or replaced by another animation on the same value. For a like button that users tap repeatedly, the `false` case is common, so side effects in the callback need that check.

  • In React Native Animated, when would you pick Animated.decay over timing or spring?
    When the motion starts from a velocity rather than aiming at a target, such as flinging a card or a sticker after a gesture. `decay` takes the initial `velocity` and slows it with `deceleration` (0.998 by default) until it stops, so the end position depends on how hard it was thrown. `timing` and `spring` both need a `toValue`.
  • Why use useAnimatedValue instead of new Animated.Value() in a function component?
    `new Animated.Value(1)` in the component body creates a new value on every render, so any running animation is lost and the view jumps back to the initial value. `useAnimatedValue(1)` creates the value once and returns the same instance on later renders; it is the hook form of keeping the value in a ref.

Handing a musician the full sheet music before the concert instead of whispering each note: once the score is on stage, the performance continues even if the conductor gets distracted.

saying these in an interview costs you the question

  • useNativeDriver defaults to true, so you can leave it out
  • The native driver sends a message to native on every frame
  • Animated.spring and Animated.timing both require a duration
  • A plain View will apply an Animated.Value placed in its style
  • The native driver can animate any style property, including width
open as a page

In React Native 0.87, which style properties can Animated's native driver animate, and what happens if you native-drive a heart's width?

level: middleimportance: must knowfreq 62%

basics

~20 s

By default the native driver animates only non-layout styles: transform, opacity, colours, border radii, shadow opacity and radius, elevation, zIndex and filter. Native-driving width logs an error that the style is not supported; animate transform: scale instead.

open as a page

In React Native Gesture Handler 3, when do you use useSimultaneousGestures, useExclusiveGestures and useCompetingGestures to combine gestures on one view?

level: middleimportance: must knowfreq 45%

basics

~20 s

useSimultaneousGestures lets all gestures be active together, useExclusiveGestures gives them a priority order where each waits for the ones before it to fail, and useCompetingGestures lets the first to activate cancel the rest. All three combine gestures on one detector.

open as a page

In React Native, why does a Gesture Handler pan keep tracking the finger while the JavaScript thread is busy, when a PanResponder drag stalls?

level: middleimportance: must knowfreq 55%

basics

~20 s

Gesture Handler recognises gestures natively on the UI thread, and with Reanimated installed its hook callbacks run as worklets on the UI thread too. PanResponder decides and reacts in JavaScript, so a busy JS thread delays every move.

open as a page

In Reanimated 4, how do useSharedValue and useAnimatedStyle move a view on every frame without re-rendering the React component?

level: middleimportance: must knowfreq 62%

basics

~20 s

A shared value is a mutable container kept in sync between the JS and UI runtimes. useAnimatedStyle's updater is a UI-runtime worklet that re-runs when a shared value it reads changes, so view props update without a React render.

open as a page

In React Native's gesture responder system, how does a View become the touch responder, and which callbacks fire as it keeps or loses the touch?

level: middleimportance: must knowfreq 40%

basics

~10 s

A View returns true from onStartShouldSetResponder or onMoveShouldSetResponder, then gets onResponderGrant, or onResponderReject if the holder refuses. It receives onResponderMove, and ends with onResponderRelease, or onResponderTerminate if the touch is taken away.

open as a page

In React Native, why does a collapsing header driven by onScroll and setState stutter, and how do you move it off the JS thread?

level: middleimportance: must knowfreq 58%

basics

~20 s

onScroll runs on the JS thread, so setState per event adds a render per frame that lags whenever JS is busy. Drive the header from the offset on the UI thread instead: Animated.event with useNativeDriver: true, or Reanimated's useAnimatedScrollHandler.

open as a page

In a Reanimated 4 bottom sheet for a maps app, why does calling setState from a gesture or spring callback throw, and when is scheduleOnRN needed?

level: seniorimportance: must knowfreq 45%

basics

~20 s

Gesture callbacks and withSpring completion callbacks are worklets on the UI runtime, where React state setters cannot be called. scheduleOnRN(fn, ...args) queues an RN-runtime function to run there; use it at discrete events like a finished snap.

open as a page

In React Native, why does a PanResponder drawing stroke trail behind the finger when the JavaScript thread is busy, and how do you fix it?

level: seniorimportance: must knowfreq 55%

basics

~20 s

Every responder decision and PanResponder callback runs on the JavaScript thread, so moves wait behind other JS work and a re-render before the stroke appears. Keep move handlers cheap, or move the gesture to the UI thread.

open as a page

In React Native Gesture Handler 3, what do GestureHandlerRootView and GestureDetector each do, and how do you attach a pan gesture to a view?

level: juniorimportance: should knowfreq 45%

basics

~20 s

GestureHandlerRootView, placed near the app root, lets Gesture Handler intercept touches for everything inside it. You build a gesture with a hook such as usePanGesture and wrap the target view in a GestureDetector that receives it through its gesture prop.

open as a page

In Reanimated 4, when do you animate a shared value with withTiming rather than withSpring, and what do their defaults do?

level: juniorimportance: should knowfreq 50%

basics

~20 s

Use withTiming for a fixed duration and curve (default 300 ms, Easing.inOut(Easing.quad)) and withSpring for motion that should feel physical or continue a gesture's velocity. Reanimated 4's default spring is critically damped, so it settles without oscillating.

open as a page

In React Native, how do you wire PanResponder.create to a View so a finger can draw, and why keep the instance in useRef?

level: juniorimportance: should knowfreq 42%

basics

~20 s

Call PanResponder.create with onStartShouldSetPanResponder returning true plus move and release callbacks, then spread its panHandlers onto a View. Keep the instance in useRef so re-renders do not swap in new handlers and a fresh gestureState mid-stroke.

open as a page

In a React Native horizontal ScrollView, when do you use pagingEnabled, snapToInterval or snapToOffsets to build a snapping carousel?

level: juniorimportance: should knowfreq 42%

basics

~20 s

pagingEnabled stops on multiples of the scroll view's own size, so it suits full-width pages. snapToInterval stops on multiples of a value such as card width plus gap, and snapToOffsets stops at explicit offsets for cards of different widths.

open as a page

In React Native Animated, how do sequence, parallel, stagger and loop differ, and what happens when a child animation is interrupted?

level: middleimportance: should knowfreq 40%

basics

~20 s

sequence runs animations one after another, parallel starts them together, stagger starts them in parallel with increasing delays, and loop repeats one animation. An interrupted child stops a sequence, and stops all of a parallel unless stopTogether is false.

open as a page

In React Native Animated, what does interpolate do, and why can the default extrapolate make a springy heart shrink below its resting size?

level: middleimportance: should knowfreq 45%

basics

~20 s

interpolate maps a value's inputRange onto an outputRange, piecewise and linear by default. Outside the range it extends the edge segment unless told otherwise, so a spring overshooting past 1 pushes the output beyond the last value; extrapolate: 'clamp' fixes that.

open as a page

In React Native Gesture Handler 3, what separates onBegin, onActivate, onDeactivate and onFinalize, and what does event.canceled tell you?

level: middleimportance: should knowfreq 35%

basics

~20 s

onBegin fires when a handler starts tracking a touch and onFinalize always follows it; onActivate fires when activation criteria are met and onDeactivate always follows that. event.canceled is true when the gesture failed or was interrupted.

open as a page

In Reanimated 4, how do the entering, exiting and layout props animate a view as it mounts, unmounts or changes position?

level: middleimportance: should knowfreq 32%

basics

~20 s

entering and exiting take preset animations such as FadeInDown or FadeOut that play when an Animated component mounts or unmounts; layout takes a transition such as LinearTransition that animates size and position changes after React re-lays out the view.

open as a page

Why does Reanimated 4 need react-native-worklets, the Worklets Babel plugin and the New Architecture before any worklet runs?

level: middleimportance: should knowfreq 38%

basics

~20 s

Reanimated 4 moved its worklet runtime into react-native-worklets, whose Babel plugin turns worklet functions into serializable objects the UI runtime can run. It also dropped the legacy Paper renderer, so it runs only on the New Architecture.

open as a page

In React Native's responder system, when a parent View and its child both want a touch, which wins, and how do Capture handlers change it?

level: middleimportance: should knowfreq 30%

basics

~10 s

By default the deepest View wins, because should-set handlers bubble up from the touched child. A parent can claim first with onStartShouldSetResponderCapture or onMoveShouldSetResponderCapture, which run top-down before the bubble phase.

open as a page

In React Native Gesture Handler 3, how do you build swipe-to-delete episode rows with ReanimatedSwipeable and keep only one row open?

level: middleimportance: should knowfreq 30%

basics

~20 s

Wrap each row in ReanimatedSwipeable and render the delete button with renderRightActions, animating it from the progress and translation shared values. Keep a ref to the open row and call close() on it when another row starts opening.

open as a page

In Reanimated 4, how does useAnimatedScrollHandler drive a collapsing podcast header, and which scroll events can it handle?

level: middleimportance: should knowfreq 36%

basics

~20 s

useAnimatedScrollHandler returns a handler whose worklets run on the UI runtime for onScroll, onBeginDrag, onEndDrag, onMomentumBegin and onMomentumEnd. Pass it to onScroll of Reanimated's Animated.ScrollView or Animated.FlatList, write the offset to a shared value, and interpolate the header.

open as a page

In a React Native Animated like-button, rapid taps leave the heart at the wrong scale and completion logic misfires; what is going on, and how do you make it robust?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Each new animation on a value stops the running one, whose callback gets finished: false, and an interrupted sequence stops mid-way. Keep the like state in React, stop and reset before each run, and act only when finished is true.

open as a page

In React Native Gesture Handler 3 with Reanimated installed, why does calling setState from a gesture's onDeactivate throw, and how do you fix it?

level: seniorimportance: should knowfreq 33%

basics

~20 s

With Reanimated installed, inline gesture callbacks run as worklets on the UI thread, and a React state setter captured from the JS runtime cannot be called synchronously there. Set runOnJS: true on the gesture, or schedule the call back to the JS thread.

open as a page

In React Native with Gesture Handler 3, how do you stop a pinch-to-zoom photo inside a horizontal pager from fighting the pager's swipe?

level: seniorimportance: should knowfreq 40%

basics

~20 s

Use Gesture Handler's ScrollView as the pager so it has a native gesture, then give each photo's pinch and pan block: scrollGesture so the pager waits for them. Enable the pan only while zoomed, so unzoomed swipes still page.

open as a page

In React Native, a PanResponder drawing canvas inside a vertical ScrollView keeps losing strokes to scrolling; why, and how do you stop it?

level: seniorimportance: should knowfreq 35%

basics

~20 s

The ScrollView competes on two layers: it asks for the JS responder once it scrolls, and the native scroll gesture can cancel the touch. Refuse in onPanResponderTerminationRequest, keep the Android block default, and disable scrollEnabled while a stroke is in progress.

open as a page

In React Native, how do you build a podcast header that hides on scroll down and returns on scroll up, and why does iOS bounce break a naive diffClamp?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Animated.diffClamp accumulates scroll deltas within 0 and the header height, so scrolling down hides the header and scrolling up reveals it. iOS bounce feeds it offsets outside the content, so clamp the scroll value first.

open as a page

In React Native, what does LayoutAnimation.configureNext do, and is UIManager.setLayoutAnimationEnabledExperimental still needed on Android in 0.87?

level: middleimportance: nice to knowfreq 22%

basics

~20 s

LayoutAnimation.configureNext, called just before a state change, animates every layout change in the next commit: views created, moved, resized or removed. Under the New Architecture, the only architecture since 0.82, UIManager.setLayoutAnimationEnabledExperimental is a no-op that only logs a warning.

open as a page

In a React Native PanResponder, why can reading evt.nativeEvent or gestureState inside a setState updater or setTimeout give null or zeros?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Both objects are reused. React Native's responder events are pooled, so nativeEvent is nulled once the handler returns, and PanResponder mutates one gestureState in place and resets it to zeros after release or terminate. Copy the numbers out synchronously.

open as a page