Skip to content

Conversation

andrepimenta
Copy link
Member

Description

This PR implements Worklets. Worklets are small JavaScript functions that can be executed on a separate JavaScript Thread so they don't clog the main JS Thread. Perfect for heavy computations and processing.

Screenshots/Recordings

Before

Before.-.worklets.disabled.mov

After

After.-.worklets.enabled.mov

How to use RN Worklets (Threads)

runAsync

Purpose: runAsync is used to execute code on a different thread without blocking the main JS thread.

When to use: Use it to offload functions to a background thread.

Example:

import { Worklets } from 'react-native-worklets-core';
...
const fibonacci = (num: number): number => {
  'worklet'
  if (num <= 1) return num;
  let prev = 0, curr = 1;
  for (let i = 2; i <= num; i++) {
    let next = prev + curr;
    prev = curr;
    curr = next;
  }
  return curr;
}

const context = Worklets.defaultContext
const result = await context.runAsync(() => {
  'worklet'
  return fibonacci(50)
})
console.log(`Fibonacci of 50 is ${result}`)

runOnJS

Purpose: runOnJS is used to call a JavaScript function from within a worklet. Since worklets run on a separate thread, they cannot directly call JavaScript functions. runOnJS bridges this gap by allowing you to invoke JavaScript functions on the JavaScript thread.

When to use: Use it when you need to communicate from the worklet thread back to the JavaScript thread.

Example:

import { runOnJS } from 'react-native-worklets-core';
...
const [age, setAge] = useState(30)

function something() {
  'worklet'
  Worklets.runOnJS(() => setAge(50))
}

useWorklet

Purpose: useWorklet is a hook that allows you to define a worklet function directly within your React component. It ensures that the function is properly marked as a worklet and can run on the worklet thread.

When to use: Use this hook when you need to define a function that will execute on the worklet thread.

Example:

import { useWorklet } from 'react-native-worklets-core';

function MyComponent() {
  const myWorklet = useWorklet(() => {
    'worklet';
    console.log('This is running on the worklet thread');
  }, []);

  return (
    <Button title="Run Worklet" onPress={() => myWorklet()} />
  );
}

useRunOnJS

Purpose: useRunOnJS is a hook that allows you to define a JavaScript function that can be safely called from a worklet. It ensures that the function is properly wrapped to run on the JavaScript thread when invoked from the worklet thread.

When to use: Use this hook when you need to call a JavaScript function from a worklet.

import { useRunOnJS } from 'react-native-worklets-core';

function App() {
  const sayHello = useRunOnJS(() => {
    console.log("hello from JS!")
  }, [])

  const worklet = useWorklet('default', () => {
    'worklet'
    console.log("hello from worklet!")
    sayHello()
  }, [sayHello])

  return (
    <Button title="Run Worklet" onPress={() => worklet()} />
  );
}

useSharedValue

Purpose:
useSharedValue is a hook that creates a SharedValue instance, which can be read from and written to by both the JavaScript thread and the worklet thread simultaneously.

For arrays and objects, useSharedValue creates a C++-based proxy implementation, ensuring that all read and write operations on these data structures are thread-safe.

When to use:
Use useSharedValue when you need to share state between the JavaScript thread and the worklet thread.

Example:

function App() {
  const something = useSharedValue(5)
  const worklet = useWorklet('default', () => {
    'worklet'
    something.value = Math.random()
  }, [something])
}

Separate Threads/Contexts (Worklets.createContext)

Purpose:
Worklets.createContext is a method that allows you to create a separate thread (context) for running worklets.

Each thread created with createContext operates in isolation, ensuring that tasks in one thread do not interfere with tasks in another.

When to use:
Use Worklets.createContext when you need to offload heavy computations or background tasks to multiple separate threads.

const context1 = Worklets.createContext('my-new-thread-1')
const context2 = Worklets.createContext('my-new-thread-2')
context1.runAsync(() => {
  'worklet'
  console.log("Hello from context #1!")
  context2.runAsync(() => {
    'worklet'
    console.log("Hello from context #2!")
  })
})

Worklet Examples

WorkletExampleComponent.tsx

import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { useRunOnJS, useSharedValue, useWorklet } from 'react-native-worklets-core';

const WorkletExampleComponent = () => {

    const sharedValue = useSharedValue(0);

    const [result, setResult] = useState(0);

    const setResultFromWorklet = useRunOnJS(() => {
        console.log("Calling main JS from worklet!")
        setResult(sharedValue.value)
    }, [])

    const heavyComputationWorklet = useWorklet('default', () => {
        'worklet'
        console.log("Calling worklet!")
        let result = 0;
        for (let i = 0; i < 10000000; i++) {
            result += Math.random();
        }
        sharedValue.value += result;
        setResultFromWorklet()
    }, [sharedValue, setResultFromWorklet])

    const mainJSFunction = () => {
        console.log("hello from main JS function!", sharedValue.value)
        heavyComputationWorklet()
    }

    return (
        <View style={styles.container}>
            <Text style={styles.title}>Worklet Hooks Example</Text>

            <View style={styles.infoBox}>

                <TouchableOpacity style={styles.tertiaryButton} onPress={mainJSFunction}>
                    <Text style={styles.buttonText}>Heavy Computation</Text>
                </TouchableOpacity>
            </View>

            <View style={styles.infoBox}>
                <Text style={styles.messageText}>Result: {result}</Text>
            </View>

            <View style={styles.descriptionBox}>
                <Text style={styles.descriptionText}>
                    This component demonstrates:
                    {'\n'}• useSharedValue: Shared counter between threads
                    {'\n'}• useWorklet: Functions running on worklet thread
                    {'\n'}• useRunOnJS: Safe JS calls from worklets
                </Text>
            </View>
        </View>
    );
};

const styles = StyleSheet.create({
    container: {
        padding: 16,
        backgroundColor: '#fff',
    },
    title: {
        fontSize: 24,
        fontWeight: 'bold',
        textAlign: 'center',
        marginBottom: 16,
    },
    infoBox: {
        marginBottom: 24,
        padding: 16,
        backgroundColor: '#f5f5f5',
        borderRadius: 8,
    },
    counterText: {
        fontSize: 16,
        fontWeight: '600',
        marginBottom: 8,
    },
    messageText: {
        fontSize: 14,
        color: '#666',
    },
    buttonContainer: {
        gap: 12,
    },
    primaryButton: {
        backgroundColor: '#007AFF',
        padding: 16,
        borderRadius: 8,
        alignItems: 'center',
    },
    secondaryButton: {
        backgroundColor: '#34C759',
        padding: 16,
        borderRadius: 8,
        alignItems: 'center',
    },
    tertiaryButton: {
        backgroundColor: '#FF9500',
        padding: 16,
        borderRadius: 8,
        alignItems: 'center',
    },
    buttonText: {
        color: '#fff',
        fontSize: 16,
        fontWeight: '600',
    },
    descriptionBox: {
        padding: 16,
        backgroundColor: '#f0f0f0',
        borderRadius: 8,
    },
    descriptionText: {
        fontSize: 12,
        color: '#666',
        lineHeight: 18,
    },
});

export default WorkletExampleComponent;

WorkletExampleFunctions.ts

import { Worklets } from 'react-native-worklets-core';

// Example 1: Heavy computation using runAsync
const fibonacci = (num: number): number => {
  'worklet';
  if (num <= 1) return num;
  let prev = 0, curr = 1;
  for (let i = 2; i <= num; i++) {
    let next = prev + curr;
    prev = curr;
    curr = next;
  }
  return curr;
};

export const calculateFibonacci = async (num: number): Promise<number> => {
  const context = Worklets.defaultContext;
  const result = await context.runAsync(() => {
    'worklet';
    return fibonacci(num);
  });
  return result;
};

// Example 2: Array processing on worklet thread
export const processLargeArray = async (data: number[]): Promise<number[]> => {
  const context = Worklets.defaultContext;
  
  return await context.runAsync(() => {
    'worklet';
    // Heavy array processing that would block the UI thread
    return data
      .map(x => x * 2)
      .filter(x => x > 10)
      .map(x => Math.sqrt(x))
      .sort((a, b) => b - a);
  });
};

// Example 3: Using separate contexts for parallel processing
export const parallelProcessing = async (
  data1: number[],
  data2: number[]
): Promise<{result1: number, result2: number}> => {
  const context1 = Worklets.createContext('worker-1');
  const context2 = Worklets.createContext('worker-2');
  
  // Process both arrays in parallel on different threads
  const [result1, result2] = await Promise.all([
    context1.runAsync(() => {
      'worklet';
      console.log('Processing on worker-1');
      return data1.reduce((sum, val) => sum + Math.pow(val, 2), 0);
    }),
    context2.runAsync(() => {
      'worklet';
      console.log('Processing on worker-2');
      return data2.reduce((sum, val) => sum + Math.pow(val, 3), 0);
    })
  ]);
  
  return { result1, result2 };
}; 

Pre-merge author checklist

Pre-merge reviewer checklist

  • I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed).
  • I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.

@metamaskbot metamaskbot added the team-mobile-platform Mobile Platform team label Aug 25, 2025
@andrepimenta andrepimenta marked this pull request as ready for review August 25, 2025 16:14
@andrepimenta andrepimenta requested a review from a team as a code owner August 25, 2025 16:14
@metamaskbot metamaskbot added the INVALID-PR-TEMPLATE PR's body doesn't match template label Aug 25, 2025
@andrepimenta andrepimenta added Run Smoke E2E Requires smoke E2E testing and removed size-M labels Aug 25, 2025
Copy link
Contributor

github-actions bot commented Aug 25, 2025

https://bitrise.io/ Bitrise

❌❌❌ pr_smoke_e2e_pipeline failed on Bitrise! ❌❌❌

Commit hash: 4a96bc8
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/2a43b13d-a165-4e50-8afa-a61a5125e230

Note

  • You can rerun any failed steps by opening the Bitrise build, tapping Rebuild on the upper right then Rebuild unsuccessful Workflows
  • You can kick off another pr_smoke_e2e_pipeline on Bitrise by removing and re-applying the Run Smoke E2E label on the pull request

Tip

  • Check the documentation if you have any doubts on how to understand the failure on bitrise

Copy link

socket-security bot commented Aug 25, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​string-hash-64@​1.0.31001006176100
Addednpm/​react-native-worklets-core@​1.6.0991008884100

View full report

@andrepimenta andrepimenta added the No QA Needed Apply this label when your PR does not need any QA effort. label Aug 25, 2025
cursor[bot]

This comment was marked as outdated.

@andrepimenta andrepimenta removed the Run Smoke E2E Requires smoke E2E testing label Aug 25, 2025
@andrepimenta andrepimenta added Run Smoke E2E Requires smoke E2E testing and removed size-M labels Aug 25, 2025
Copy link
Contributor

https://bitrise.io/ Bitrise

🔄🔄🔄 pr_smoke_e2e_pipeline started on Bitrise...🔄🔄🔄

Commit hash: 07aa682
Build link: https://app.bitrise.io/app/be69d4368ee7e86d/pipelines/21683106-ae0d-424b-8d04-618cef374c67

Note

  • This comment will auto-update when build completes
  • You can kick off another pr_smoke_e2e_pipeline on Bitrise by removing and re-applying the Run Smoke E2E label on the pull request

cursor[bot]

This comment was marked as outdated.

Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
INVALID-PR-TEMPLATE PR's body doesn't match template No QA Needed Apply this label when your PR does not need any QA effort. Run Smoke E2E Requires smoke E2E testing size-M team-mobile-platform Mobile Platform team
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants