Skip to content
Back to Blog
Mobile Development

Advanced Usage of Worklets for High-Performance React Native Applications

4 min read
By Skynor Labs Team
Advanced Usage of Worklets for High-Performance React Native Applications

A drag interaction can feel delayed when its position depends on frequent React state updates and the JavaScript runtime is busy. Reanimated worklets let small animation calculations run on the UI runtime, so those updates can avoid a React render on every frame. They do not remove the cost of drawing views, decoding images or running expensive UI code.

The useful question is which work belongs on that runtime, and how to check whether moving it improves the interaction.

Check the project's setup first

Use the Reanimated installation guide for the architecture and package versions in your application. Follow its compatibility links before upgrading an existing app.

The current setup uses the separate react-native-worklets package. Where a Babel plugin must be configured manually, the documented plugin is react-native-worklets/plugin, listed last. Expo configuration depends on the project's SDK and Babel preset; avoid copying a second plugin entry into an already configured project.

Native dependency changes require a compatible native build. A Metro restart alone cannot update the native libraries installed on a device. If initialization fails, check the troubleshooting guide for package, plugin and native-build mismatches before changing animation code.

Keep frame updates separate from React state

This small example starts an animation from a button. React state records the selected position once per press. A shared value supplies the animated transform without setting React state on each frame.

import { useState } from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import Animated, {
  ReduceMotion,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

export function MotionPreview() {
  const [shifted, setShifted] = useState(false);
  const progress = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => ({
    transform: [{ translateX: progress.value * 72 }],
  }));

  function togglePosition() {
    const next = !shifted;
    setShifted(next);
    progress.value = withTiming(next ? 1 : 0, {
      duration: 240,
      reduceMotion: ReduceMotion.System,
    });
  }

  return (
    <View style={styles.container}>
      <Pressable
        accessibilityRole="button"
        accessibilityLabel={shifted ? 'Move preview left' : 'Move preview right'}
        onPress={togglePosition}
      >
        <Text>Move preview</Text>
      </Pressable>
      <Animated.View style={[styles.preview, animatedStyle]} />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { padding: 24, gap: 24 },
  preview: { width: 72, height: 72, borderRadius: 12, backgroundColor: '#78b3c7' },
});

The callback passed to useAnimatedStyle is automatically workletized. Other supported callbacks have the same behavior; a separate 'worklet' directive is needed when defining worklets outside those automatically handled contexts. See the worklets guide before moving arbitrary functions across runtimes.

The duration is an example design choice, not a performance target. The timing API supports the system's reduced-motion preference. Confirm the resulting behavior in the accessibility settings of the devices you support.

Keep the UI work small

A gesture's position, a bounded interpolation and a transform are reasonable worklet responsibilities. Sorting a large response, parsing a payload, running business rules or preparing analytics events inside that callback can compete with the UI work the user is waiting for.

Worklets capture values from their surrounding scope. Extract the few primitives a callback needs rather than closing over a large configuration object or dataset. Check captured values when a small callback unexpectedly causes extra serialization or memory use.

Keep animation progress in shared values. Send meaningful events such as a completed selection back to application state, rather than forwarding every intermediate position.

Cross runtimes at deliberate points

When a worklet needs to update React state, navigate or call an application callback, use the API supported by the installed Worklets version. The current scheduleOnRN documentation describes scheduling a function on the React Native runtime.

The target function must be defined in that runtime's scope, such as the component body. Creating it inside a UI-runtime callback and then scheduling it back is not equivalent. Pass only the arguments the application needs, and account for an animation being cancelled before treating its completion as a committed user action.

Verify the interaction under realistic load

Record the same interaction before and after the change. Include a longer list, concurrent data updates, an interrupted gesture and a return from the background. Keep the build, dataset and device settings consistent between comparisons.

Use React Native DevTools to investigate JavaScript and React work. Use native profiling tools when the delay is in layout, drawing or native allocations, then confirm the behavior in a release build on physical iOS and Android devices.

A worklet can keep an animation independent of React's update path while the UI thread still misses frames. Accept the change based on observed interaction latency and frame behavior on the agreed devices, with reduced motion enabled as part of the check.

For a slow gesture or transition in an existing app, our React Native performance audit starts with a reproducible flow and device trace. Share the affected screen so we can assess the scope.

Discuss your engineering priorities

Tell us what your product does, what is getting in the way and what you need to ship next. We can review the scope together.

Discuss a project