I’m using React Native Gesture Handler into a js file and when I run the code I receive this error
ERROR [react-native-gesture-handler] Some of the callbacks in the gesture are worklets and some are not. Either make sure that all calbacks are marked as ‘worklet’ if you wish to run them on the UI thread or use ‘.runOnJS(true)’ modifier on the gesture explicitly to run all callbacks on the JS thread.
CODE:
` import * as React from ‘react’;
import { View, Text, Dimensions } from ‘react-native’;
import styles from ‘../styles/style_home’
import * as Animatable from ‘react-native-animatable’
import {Canvas, Path, Skia} from ‘@shopify/react-native-skia’;
import {curveBasis, line, scaleLinear, scalePoint} from ‘d3’;
import DataPalmas from ‘../../assets/csvjson.json’
import Gradient from ‘./Gradient’
import XAxisText from ‘./XAxisText’;
import { clamp, runOnJS, useSharedValue, withDelay, withTiming } from ‘react-native-reanimated’;
import Cursor from ‘./Cursor’
import {
Gesture,
GestureDetector,
PanGestureHandlerEventPayload,
} from ‘react-native-gesture-handler’;
import {getYForX, parse} from ‘react-native-redash’;
export default function () {
const [showCursor, setShowCursor] = React.useState(false)
const animationLine = useSharedValue(0)
const animationGradient = useSharedValue({x: 0, y: 0})
const cx = useSharedValue(0)
const cy = useSharedValue(0)
React.useEffect(() => {
animationLine.value = withTiming(1, {duration:2000})
animationGradient.value = withDelay(2000, withTiming({x: 0, y: chartHeight}, {duration: 1000}))
}, [])
const chartHeight = 150
const chartWidth = Dimensions.get('window').width
const chartMargin = 20
const data = DataPalmas
const xDomain = data.map((dataPoint) => dataPoint.Data)
const xRange = [chartMargin, chartWidth - chartMargin]
const x = scalePoint().domain(xDomain).range(xRange).padding(0)
const stepX = x.step()
const max = Math.max(...data.map(val => val.Precipitacao))
const min = Math.min(...data.map(val => val.Precipitacao))
const yDomain = [min, max]
const yRange = [chartHeight, 0]
const y = scaleLinear().domain(yDomain).range(yRange)
const curvedLine = line()
.x(d => x(d.Data))
.y(d => y(d.Precipitacao))
.curve(curveBasis)(data)
const linePath = Skia.Path.MakeFromSVGString(curvedLine)
const path = parse(linePath.toSVGString())
function handleGestureEvent(e){
const clampValue = clamp(
Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
chartMargin,
chartWidth - chartMargin,
)
cx.value = clampValue
cy.value = getYForX(path, Math.floor(clampValue))
}
const pan = Gesture.Pan()
.onTouchesDown(() => {
setShowCursor(true)
})
.onTouchesUp(() => {
setShowCursor(false)
})
.onBegin(handleGestureEvent)
.onChange(handleGestureEvent)
return (
<Animatable.View style={styles.container__graphic} animation={"fadeInLeft"}>
<GestureDetector gesture={pan}>
<Canvas
style={{
width: chartWidth,
height: chartHeight+30,
}}>
<Path
path={linePath}
style={'stroke'}
strokeWidth={2}
color={'#0bb861'}
strokeCap={'round'}
start={0}
end={animationLine}/>
<Gradient
chartHeight = {chartHeight}
chartMargin = {chartMargin}
chartWidth = {chartWidth}
curvedLine = {curvedLine}
animationGradient = {animationGradient}
/>
{data.map((dataPoint, index) => (
<XAxisText
x={x(dataPoint.Data)}
y={chartHeight}
text={dataPoint.Data}
key={index}
/>
))}
{showCursor &&
<Cursor
cx = {cx}
cy = {cy}
chartHeight = {chartHeight}
/>}
</Canvas>
</GestureDetector>
</Animatable.View>
);
}`
When I use ‘worklet’ into this function the app crashes when I click
function handleGestureEvent(e){
**'worklet'**
const clampValue = clamp(
Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
chartMargin,
chartWidth - chartMargin,
)
cx.value = clampValue
cy.value = getYForX(path, Math.floor(clampValue))
}
2
Answers
You need to add
.runOnJS(true)
to yourGesture.Pan()
call, like this:Reanimated’s babel plugin workletizes the callbacks automatically when the callbacks are chained. Using
.runOnJS(true)
will force every callback in the chain to run on js thread which might have negative performance impact if there is a callback in the chain with animation logic inside so it would be more optimal to userunOnJS
function selectively for functions that should run on JS thread (e.g. state setters).The crash with manually workletized
handleGestureEvent
is another thing which might be a bug insidereact-native-reanimated
(e.g. this one). I’d try to updatereact-native-reanimated
to the latest version or move the code of thehandleGestureEvent
into.onBegin
and into.onChange
callbacks directly, leveraging the automatic workletization, and mark the duplicated logic with FIXME (or, even better, create a tech debt ticket) to refactor it later when the bug is located and fixed.