React Native Reanimated crash: resolve inputs before the worklet
An intro transition closed the React Native app. The recorded incident had no red error screen and no error-boundary fallback. I traced the change in FlowSlide: its custom entering animation called a responsive helper from the UI runtime.
This is Day 22 of Ship Native / Production fixes. I reviewed the source and teaching example; I did not execute the app or reproduce the crash. The video uses synthetic English narration and source-based illustrations. The companion video becomes viewable when its YouTube release goes public.
Resolve inputs on the JavaScript thread, then capture them in the worklet.
The line that crossed runtimes
FlowSlide fades an intro step in while translating from the inline start. Its offset is a scaled 24 points, negated for right-to-left layouts. The old custom entering function calculated that offset inside a function marked with worklet:
// Source-derived failure pattern, shortened for teaching.
const slideIn = () => {
'worklet';
const from = I18nManager.isRTL ? -SW(24) : SW(24);
// Return initial values and timing animations.
};
SW looks simple from its signature: a number enters and a number returns. Its implementation reads React Native Dimensions and PixelRatio. Those dependencies explain why the execution context matters. An ordinary native-backed helper cannot be called synchronously from a UI worklet just because it is imported.
The manifest declares React Native 0.86.0, Reanimated ^4.5.0 and Worklets ^0.12.1. Installed versions and the exact device build are unknown. I would check those versions before integrating the example into another project.
What a worklet captures
Reanimated prepares functions for a separate JavaScript runtime. The Worklets plugin captures referenced outer values. That allows a numeric offset prepared on the React Native runtime to be used when a UI animation starts. The Reanimated worklet guide describes this closure behavior.
The correction computes OFFSET at module scope and prepares TIMING there too. The worklet then returns initial values and uses supported withTiming calls. Its timing configuration includes supported easing. It is not a JSON-only object, and the fix does not prohibit every function call in a worklet.
I keep ordinary React Native input preparation outside the worklet and use the supported animation APIs inside it. This is a useful boundary to name in code review.
Why the error boundary stayed silent
The project account says the error escaped Hermes and aborted the process. No retained stack trace confirms the precise exception path, and the build type is unknown. Another development build or library version may surface a worklet error differently.
A React error boundary protects rendering below it. It does not wrap a separate runtime or catch every asynchronous callback. TypeScript also checks the shapes of the values rather than proving that each dependency can execute on the UI runtime. I would collect native crash information for a suspected process failure instead of relying only on a JavaScript handler.
The Worklets troubleshooting guide documents failures from synchronously calling ordinary functions on the UI thread. That supports the boundary explanation; it does not establish that all such errors must terminate every app.
A responsive teaching adaptation
The source uses module-level constants. Its language-switch flow restarts the app, which refreshes the captured direction. Rotation or tablet split view can change window width without reloading that module.
For a width-sensitive transition, I resolve inputs in the component and recreate the entering function for future entries. The host passes direction explicitly. The width baseline and timing below are illustrative design tokens, and the integration assumes compatible native libraries and a configured Worklets plugin.
import React, { useMemo } from 'react';
import { useWindowDimensions, type StyleProp, type ViewStyle } from 'react-native';
import Animated, { Easing, ReduceMotion, withTiming } from 'react-native-reanimated';
/** Reviewed integration example, not device-tested.
* Requires compatible native Reanimated/Worklets and configured Worklets plugin.
* Host supplies resolved direction; changing native RTL may require a restart.
* Source-derived: prepare inputs outside entering worklet; honor reduced motion.
* Adaptation: component width instead of source's module-level responsive helper.
* 390 and 220 are illustrative design tokens, not production measurements.
*/
export function FlowSlideExample({ children, stepKey, isRTL, style }: {
children: React.ReactNode;
stepKey: string;
isRTL: boolean;
style?: StyleProp<ViewStyle>;
}) {
const { width } = useWindowDimensions();
const scaled = 24 * width / 390;
const offset = isRTL ? -scaled : scaled;
const entering = useMemo(() => {
// All ordinary React Native inputs are read on the JS runtime.
const timing = {
duration: 220,
easing: Easing.bezier(0.22, 1, 0.36, 1),
reduceMotion: ReduceMotion.System,
};
return () => {
'worklet';
// Supported easing/animation APIs are worklet-safe library values.
return {
initialValues: { opacity: 0, transform: [{ translateX: offset }] },
animations: {
opacity: withTiming(1, timing),
transform: [{ translateX: withTiming(0, timing) }],
},
};
};
}, [offset]);
// New width updates future entering animations; not an in-flight transition.
// Stable unique step keys trigger intended entry, never translated labels.
return <Animated.View key={stepKey} entering={entering} style={style}>
{children}
</Animated.View>;
}
Changing width prepares the next entering animation. It does not retarget an in-flight transition. Continuous responsiveness may need shared values or a different animation design. The host still owns language state and any native restart required for RTL.
Use stable unique step keys. The source also fixed a duplicate-key warning caused by repeated Arabic labels, which is a separate issue from the runtime crash.
Audit the other callbacks
I would search explicit worklet directives and include automatically workletized animated-style, reaction, scroll and gesture callbacks. Callback execution depends on the API and configuration. Trace translation utilities, responsive helpers and ordinary service functions to see what they read.
If a UI callback needs a function on the React Native runtime, the current Worklets API documents asynchronous scheduleOnRN. Define that function on the RN runtime and check the installed version. It cannot provide an immediate synchronous return value for an animation calculation. The source fix here resolves the needed inputs before the worklet.
Verify the actual transition
- Confirm the installed Reanimated/Worklets versions and plugin configuration.
- Run every entering transition on a physical device in a release-like build.
- Exercise LTR and RTL, reduced motion and rapid step changes.
- Rotate or resize the window and verify the next entering animation uses the new dimensions.
- Collect device, OS, package versions, build type and native crash logs for any process failure.
- Keep deliberate failure experiments in an isolated test build, away from the shipping app.
A project note now names this runtime boundary and points contributors to FlowSlide. It helps review; it is not an automated regression test.
Previous lesson: React Native WebView and one-time login tickets. Its link will be added when published. The next topic is unassigned.
Comments