skip to Main Content

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


  1. You need to add .runOnJS(true) to your Gesture.Pan() call, like this:

        const pan = Gesture.Pan()
            .runOnJS(true)
            .onTouchesDown(() => {
                setShowCursor(true)
            })
            .onTouchesUp(() => {
                setShowCursor(false)
            })
            .onBegin(handleGestureEvent)
            .onChange(handleGestureEvent)
    
    Login or Signup to reply.
  2. 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 use runOnJS 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 inside react-native-reanimated (e.g. this one). I’d try to update react-native-reanimated to the latest version or move the code of the handleGestureEvent 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.

    const pan = Gesture.Pan()
      .onTouchesDown(runOnJS(() => {
          setShowCursor(true)
      }))
      .onTouchesUp(runOnJS(() => {
          setShowCursor(false)
      }))
      .onBegin((e) => {
        const clampValue = clamp(
            Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
            chartMargin,
            chartWidth - chartMargin,
        )
        cx.value = clampValue
        cy.value = getYForX(path, Math.floor(clampValue))
      })
      .onChange((e) => {
        const clampValue = clamp(
            Math.floor(e.absoluteX / stepX) * stepX + chartMargin,
            chartMargin,
            chartWidth - chartMargin,
        )
        cx.value = clampValue
        cy.value = getYForX(path, Math.floor(clampValue))
      })
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search