How to call useState setter inside a worklet function?

I’m trying to call the React useState setter function from within a worklet function. This callback is provided by the package – https://github.com/mgcrea/react-native-dnd which exposes an onDragEnd event.

I’ve found other SO answers suggesting to use runOnJS from react-native-reanimated in order to run JS code from within the UI thread (this worklet function). But console logs within runOnJS and in the render function itself is not updated when the handleDragEnd is emit.

What needs to change in order to call the useState setter within a worklet function?

Below is a snippet showing the worklet and some of the render function:

import {runOnJS} from 'react-native-reanimated';

  const handleDragEnd: DndProviderProps['onDragEnd'] = ({active, over}) => {
    'worklet';
    if (over) {
      console.log('onDragEnd', {active, over});
      console.log(active);

      // Create a new item based on the type of the dragged item
      const newItem = {
        partName: `${active.id.split('-')[0]}`,
      };

      runOnJS(() => {
        // Add the new item to ductItems
        setDuctItems((prevDuctItems) => {
          const updatedDuctItems = [...prevDuctItems, newItem];
          console.log('Updated ductItems:', updatedDuctItems);
          return updatedDuctItems;
        });
      });

      dynamicData.value = {
        over: over,
      };
    }
  };

  return (
    <GestureHandlerRootView>
      <View style={styles.container}>
        <DndProvider
          onDragEnd={handleDragEnd}
          >
          <Droppable id="drop" style={styles.dropBox}>
            <Text>DROP</Text>
          </Droppable>
          <View style={styles.partsContainer}>
            <Text>{JSON.stringify(ductItems)}</Text>
            {ductItems.map((item, index) => (
              <DraggableCustomComponent
                style={styles.partItem}
                id={`${item.partName}-${index}-key`}
                key={`${item.partName}-${index}-key`}
                partType={item.partName}
                data={dynamicData}
              />
            ))}
          </View>
        </DndProvider>
  );

The issue was that I wasn’t invoking runOnJs correctly according to the reanimated docs. The function uses “currying” pattern which takes the args as last param:

const replaceDuctPart = (item: {partName: string}) => {
    setDuctItems((prevDuctItems) => {
      const updatedDuctItems = [...prevDuctItems, item];
      console.log('Updated ductItems:', updatedDuctItems);
      return updatedDuctItems;
    });
  };

runOnJS(replaceDuctPart)(newItem);

Leave a Comment