Search code examples
react-nativereact-native-reanimated-v2

React native run useState/force rerender inside worklet funtion


I'm calling the useAnimatedScrollHandler hook from react-native-reanimated to handle my onScroll function on an Animated.ScrollView. This hook works as expected but I now want to disable a custom button (My FlatButton) based on the currentIndex which is a sharedValue. But when the sharedValue changes the screen doesn't get rerendered, because the state doesn't change so the look of my button remains the same.
Is there a way to force a rerender inside of a worklet, or is it possible to use useState to force a rerender from inside a worklet?

const scrollHandler = useAnimatedScrollHandler((event) => {
  translationX.value = event.contentOffset.x
  if (event.contentOffset.x < width * 0.5 && currentIndex.value != 0) {
    currentIndex.value = 0
  } else if (
    event.contentOffset.x > width * 0.5 &&
    event.contentOffset.x < width * 1.5 &&
    currentIndex.value != 1
  ) {
    currentIndex.value = 1
  } else if (event.contentOffset.x > width * 1.5 && currentIndex.value != 2) {
    currentIndex.value = 2
  }
})
<FlatButton
  label="Next"
  disabled={
    (currentIndex.value == 0 && (!firstName || !lastName)) ||
    (currentIndex.value == 1 && (!dateOfBirth || !sex)) ||
    (currentIndex.value == 2 &&
      (!streetNumber || !postalCode || !city || !state || !country))
  }
  onPress={() => {
    if (currentIndex.value == 0) {
      scrollRef.current
        ?.getNode()
        .scrollTo({ x: width, animated: true })
    } else if (currentIndex.value == 1) {
      scrollRef.current?.getNode().scrollToEnd({ animated: true })
    }
  }}
/>

Solution

  • I just found out that reanimated offers the function runOnJS which makes it possible to run a javscript function like setState inside a worklet. So just create a wrapper function, like in my case toggleIndex in which you interact with your state and call this function inside runOnJS from your worklet.

    const [currentIndex, setCurrentIndex] = useState(0)
    
    const toggleIndex = (index: number) => {
      setCurrentIndex(index)
    }
    
    const scrollHandler = useAnimatedScrollHandler((event) => {
      translationX.value = event.contentOffset.x
      if (event.contentOffset.x < width * 0.5 && currentIndex != 0) {
        runOnJS(toggleIndex)(0)
      } else if (
        event.contentOffset.x > width * 0.5 &&
        event.contentOffset.x < width * 1.5 &&
        currentIndex != 1
      ) {
        runOnJS(toggleIndex)(1)
      } else if (event.contentOffset.x > width * 1.5 && currentIndex != 2) {
        runOnJS(toggleIndex)(2)
      }
    })