diff --git a/.argent/flows/shared-value-test.yaml b/.argent/flows/shared-value-test.yaml new file mode 100644 index 0000000000..91603391a9 --- /dev/null +++ b/.argent/flows/shared-value-test.yaml @@ -0,0 +1,59 @@ +steps: + - echo: Launching Expo Example + - launch: com.example.ExpoExample + - await: { visible: { text: "Empty Example" }, timeout: 15000 } + - echo: "On the Home list, search for the shared value example" + - type: { text: "shared", into: { id: "search-examples" } } + - echo: "Tap the example to open it" + - tap: { text: "Shared Value" } + - await: { visible: { id: "shared-value-box" } } + - await: { idle: true } + + - echo: "Clear console" + - await: { visible: { id: "open-console-button" } } + - tap: { id: "open-console-button" } + - await: { visible: { id: "clear-console-button" } } + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "The gesture starts configured for a single tap, so one tap activates it" + - tap: { id: "shared-value-box" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - assert: { visible: { text: "1. onBegin" } } + - assert: { visible: { text: "2. onActivate" } } + - assert: { visible: { text: "3. onDeactivate" } } + - assert: { visible: { text: "4. onFinalize" } } + + - echo: "Clear console" + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "Raise the shared value, then check that a single tap no longer activates" + - tap: { id: "increment-taps-button" } + - tap: { id: "shared-value-box" } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - assert: { visible: { text: "5. Required taps: 2" } } + - assert: { visible: { text: "6. onBegin" } } + - assert: { visible: { text: "7. onFinalize" } } + - assert: { hidden: { text: "onActivate" } } + + - echo: "Clear console" + - tap: { id: "clear-console-button" } + - tap: { id: "close-console-button" } + - await: { idle: true } + + - echo: "A double tap matches the new configuration and activates the gesture" + - tap: { on: { id: "shared-value-box" }, times: 2 } + - tap: { id: "open-console-button" } + - await: { idle: true } + - await: { visible: { id: "close-console-button" } } + - assert: { visible: { text: "8. onBegin" } } + - assert: { visible: { text: "9. onActivate" } } + - assert: { visible: { text: "10. onDeactivate" } } + - assert: { visible: { text: "11. onFinalize" } } diff --git a/.github/actions/argent-server/action.yml b/.github/actions/argent-server/action.yml new file mode 100644 index 0000000000..a90876d56b --- /dev/null +++ b/.github/actions/argent-server/action.yml @@ -0,0 +1,68 @@ +name: Start Argent tool-server +description: >- + Installs the Argent CLI and starts a tool-server for later steps and + ARGENT_SERVER_LOG for the caller to print on failure. + +inputs: + port: + description: Port to serve the tool-server on. + required: false + default: '3001' + simulator-server-log: + description: >- + SIMSERVER_LOG level for the simulator-server backend the tool-server + spawns, e.g. `debug`. Left unset when empty. + required: false + default: '' + +runs: + using: composite + steps: + - name: Install Argent + shell: bash + run: npx @swmansion/argent init --yes + + # Start the server here and export its URL, so `flow run` reuses it instead + # of starting one in-process, which has to come up within a fixed window a + # cold runner can miss — the first run downloads the simulator-server binary. + # + # Not `--detach`: it gives the server a hard 15s to come up and then kills + # its whole process group, and backgrounding the CLI does not help because + # the timeout lives in the process we backgrounded. In the foreground there + # is no deadline and the server's own output lands in our log. + - name: Start tool-server + shell: bash + env: + TOOL_SERVER_PORT: ${{ inputs.port }} + SIMULATOR_SERVER_LOG: ${{ inputs.simulator-server-log }} + run: | + LOG="$RUNNER_TEMP/argent-server.log" + # Exported before anything can fail, so the caller's log-printing step + # can find it even when startup is what went wrong. + echo "ARGENT_SERVER_LOG=$LOG" >> "$GITHUB_ENV" + + if [ -n "$SIMULATOR_SERVER_LOG" ]; then + export SIMSERVER_LOG="$SIMULATOR_SERVER_LOG" + fi + + nohup argent server start --no-auth --port "$TOOL_SERVER_PORT" > "$LOG" 2>&1 & + SERVER_PID=$! + + echo "Waiting for Argent tool-server on :$TOOL_SERVER_PORT..." + # 3 min budget. Poll liveness too, so a server that dies on startup + # fails right away with its log instead of at the end of the budget. + for _ in $(seq 1 90); do + if ! kill -0 "$SERVER_PID" 2>/dev/null; then + echo "Argent tool-server exited during startup" >&2 + cat "$LOG" >&2 + exit 1 + fi + if curl -fsS "http://127.0.0.1:$TOOL_SERVER_PORT/tools" >/dev/null 2>&1; then + echo "tool-server is ready" + exit 0 + fi + sleep 2 + done + echo "Argent tool-server failed to become ready in time" >&2 + cat "$LOG" >&2 + exit 1 diff --git a/.github/workflows/android-e2e.yml b/.github/workflows/android-e2e.yml new file mode 100644 index 0000000000..52fe39e546 --- /dev/null +++ b/.github/workflows/android-e2e.yml @@ -0,0 +1,161 @@ +name: Run Android e2e tests + +# Reusable: runs the Argent flows against a prebuilt APK. Called by `android.yml`. +on: + workflow_call: + inputs: + artifact-name: + description: Artifact holding the APK to test. + required: true + type: string + abi: + description: Emulator ABI. Must match the ABI the APK was built for. + required: true + type: string + +jobs: + e2e: + runs-on: ubuntu-latest + timeout-minutes: 60 + + env: + # The emulator advertises its gRPC console in + # $XDG_RUNTIME_DIR/avd/running/pid_*.ini, which is where Argent's Android + # backend looks for it. The variable is unset on runners and the two fall + # back to different dirs, so pin it to a path they both check first. + XDG_RUNTIME_DIR: /tmp/xdg-runtime + AVD_NAME: test + SYSTEM_IMAGE: system-images;android-34;google_apis;${{ inputs.abi }} + + steps: + - name: checkout + uses: actions/checkout@v4 + + # sdkmanager and avdmanager are Java tools. + - name: Use Java 17 + uses: actions/setup-java@v4 + with: + distribution: oracle + java-version: 17 + + # Only needed to run the Argent CLI — the app is downloaded, not built. + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Download APK built by the build workflow + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ runner.temp }}/apk + + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + # The emulator ignores XDG_RUNTIME_DIR if it does not exist, so create it + # before booting. 0700 per the XDG spec. + - name: Prepare shared XDG_RUNTIME_DIR + run: | + mkdir -p "$XDG_RUNTIME_DIR" + chmod 700 "$XDG_RUNTIME_DIR" + + # The runner's preinstalled SDK has neither the emulator nor a system + # image (asking for platform-tools on top of it is a no-op). + - name: Install Android emulator and system image + run: | + CMDLINE_BIN=$(dirname "$(command -v sdkmanager || echo "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager")") + yes | "$CMDLINE_BIN/sdkmanager" --licenses > /dev/null || true + "$CMDLINE_BIN/sdkmanager" --install "emulator" "platform-tools" "$SYSTEM_IMAGE" > /dev/null + { + echo "$CMDLINE_BIN" + echo "$ANDROID_HOME/emulator" + echo "$ANDROID_HOME/platform-tools" + } >> "$GITHUB_PATH" + # By default avdmanager writes to $ANDROID_SDK_HOME/.android/avd while + # the emulator looks in $ANDROID_SDK_HOME/avd, which fails the boot + # with "Unknown AVD name". Both check $ANDROID_AVD_HOME first. + echo "ANDROID_AVD_HOME=$HOME/.android/avd" >> "$GITHUB_ENV" + + - name: Create AVD + run: | + mkdir -p "$ANDROID_AVD_HOME" + echo no | avdmanager create avd --name "$AVD_NAME" --package "$SYSTEM_IMAGE" --force + test -f "$ANDROID_AVD_HOME/$AVD_NAME.ini" || { + echo "avdmanager did not create $AVD_NAME.ini in $ANDROID_AVD_HOME; found instead:" >&2 + find "$HOME" "$ANDROID_HOME" -name "$AVD_NAME.ini" 2>/dev/null >&2 + exit 1 + } + + # `-grpc` makes the emulator write grpc.port to the discovery ini Argent + # reads. `-accel on` fails the boot when KVM didn't take, instead of + # falling back to a software boot slow enough to hit the job timeout. + - name: Boot emulator + run: | + nohup emulator -avd "$AVD_NAME" -no-snapshot -accel on \ + -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim \ + -grpc 8554 -camera-back none > emulator.log 2>&1 & + EMULATOR_PID=$! + adb start-server + # Poll boot state and process liveness (10 min budget): a backgrounded + # emulator that dies cannot fail the step on its own, and + # `adb wait-for-device` would just hang without saying why. + for _ in $(seq 1 120); do + if ! kill -0 "$EMULATOR_PID" 2>/dev/null; then + echo "Emulator process exited during boot:" >&2 + cat emulator.log >&2 + exit 1 + fi + if [ "$(adb shell getprop sys.boot_completed 2>/dev/null | tr -d '\r')" = 1 ]; then + BOOTED=1 + break + fi + sleep 5 + done + if [ -z "${BOOTED:-}" ]; then + echo "Emulator did not finish booting within 10 minutes" >&2 + adb devices >&2 + cat emulator.log >&2 + exit 1 + fi + adb shell input keyevent 82 + adb shell settings put global window_animation_scale 0.0 + adb shell settings put global transition_animation_scale 0.0 + adb shell settings put global animator_duration_scale 0.0 + SERIAL=$(adb devices | awk 'NR>1 && $2=="device"{print $1; exit}') + echo "Emulator ready: $SERIAL" + echo "ANDROID_SERIAL=$SERIAL" >> "$GITHUB_ENV" + + - name: Install app on emulator + run: | + APK=$(find "$RUNNER_TEMP/apk" -name '*.apk' | head -1) + if [ -z "$APK" ]; then + echo "Downloaded artifact does not contain an APK" >&2 + ls -la "$RUNNER_TEMP/apk" >&2 + exit 1 + fi + echo "Installing $APK on $ANDROID_SERIAL" + adb install -r "$APK" + + - name: Install Argent and start the tool-server + uses: ./.github/actions/argent-server + with: + # Logs how the backend discovers the emulator; drop once this is green. + simulator-server-log: debug + + - name: Run E2E tests + run: argent flow run ./.argent/flows --device "$ANDROID_SERIAL" + + - name: Print emulator log + if: ${{ !success() }} + run: cat emulator.log 2>/dev/null || echo "no emulator.log" + + # The tool-server log also carries the device backend's stderr + # (`[sim ]` lines), where a failed connection to the emulator is + # explained. + - name: Print Argent tool-server log + if: ${{ !success() }} + run: cat "$ARGENT_SERVER_LOG" 2>/dev/null || echo "no Argent tool-server log" diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6ad16bb9b2..2dde81f4c4 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -3,8 +3,11 @@ name: Build Android (expo-example) on: pull_request: paths: + - .argent/flows/* + - .github/actions/argent-server/action.yml - .github/workflows/android.yml - .github/workflows/android-build.yml + - .github/workflows/android-e2e.yml - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/android/** - packages/react-native-gesture-handler/shared/** @@ -31,3 +34,12 @@ jobs: app: expo-example artifact-name: android-apk-expo-example abi: x86_64 + + e2e: + if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: expo-example + + uses: ./.github/workflows/android-e2e.yml + with: + artifact-name: android-apk-expo-example + abi: x86_64 diff --git a/.github/workflows/ios-e2e.yml b/.github/workflows/ios-e2e.yml new file mode 100644 index 0000000000..5f4cffe427 --- /dev/null +++ b/.github/workflows/ios-e2e.yml @@ -0,0 +1,168 @@ +name: Run iOS e2e tests + +# Reusable: runs the Argent flows against a prebuilt app bundle. Called by `ios.yml`. +on: + workflow_call: + inputs: + artifact-name: + description: Artifact holding the packaged .app bundle to test. + required: true + type: string + xcode-version: + description: >- + Xcode to run on. Must match the version the app was built with, so the + simulator runtime matches the SDK. + required: true + type: string + +jobs: + e2e: + runs-on: macos-26 + timeout-minutes: 60 + + steps: + - name: checkout + uses: actions/checkout@v4 + + - name: Select Xcode + env: + XCODE_VERSION: ${{ inputs.xcode-version }} + run: | + XCODE_APP="/Applications/Xcode_${XCODE_VERSION}.app" + if [ ! -d "$XCODE_APP" ]; then + echo "Xcode ${XCODE_VERSION} is not installed on this runner. Available:" >&2 + ls -d /Applications/Xcode*.app >&2 + exit 1 + fi + echo "Using $XCODE_APP" + echo "DEVELOPER_DIR=$XCODE_APP/Contents/Developer" >> "$GITHUB_ENV" + DEVELOPER_DIR="$XCODE_APP/Contents/Developer" xcodebuild -version + + # Only needed to run the Argent CLI — the app is downloaded, not built. + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + + - name: Download app built by the build workflow + uses: actions/download-artifact@v4 + with: + name: ${{ inputs.artifact-name }} + path: ${{ runner.temp }}/app + + - name: Unpack app bundle + run: | + tar -xzf "$RUNNER_TEMP/app/app.tar.gz" -C "$RUNNER_TEMP/app" + APP_PATH=$(find "$RUNNER_TEMP/app" -maxdepth 1 -name '*.app' -type d | head -1) + if [ -z "$APP_PATH" ]; then + echo "Downloaded artifact does not contain an .app" >&2 + ls -la "$RUNNER_TEMP/app" >&2 + exit 1 + fi + echo "APP_PATH=$APP_PATH" >> "$GITHUB_ENV" + + - name: Set up iPhone 17 Pro simulator + run: | + UDID=$(xcrun simctl list devices available | grep -E "iPhone 17 Pro \(" | head -1 | grep -oE "[0-9A-Fa-f-]{36}" || true) + if [ -z "$UDID" ]; then + echo "iPhone 17 Pro not found, creating one" + RUNTIME=$(xcrun simctl list runtimes ios -j | jq -r '.runtimes | map(select(.isAvailable)) | last | .identifier') + UDID=$(xcrun simctl create "iPhone 17 Pro" "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro" "$RUNTIME") + fi + echo "Using simulator $UDID" + xcrun simctl boot "$UDID" || true + xcrun simctl bootstatus "$UDID" + echo "SIMULATOR_UDID=$UDID" >> "$GITHUB_ENV" + + - name: Disable keyboard intelligence on the simulator + run: | + KEYS=( + KeyboardPrediction + KeyboardAutocorrection + KeyboardCheckSpelling + KeyboardAutocapitalization + KeyboardPeriodShortcut + KeyboardCapsLock + KeyboardContinuousPathEnabled + ) + for key in "${KEYS[@]}"; do + xcrun simctl spawn "$SIMULATOR_UDID" defaults write "Apple Global Domain" "$key" -bool false + xcrun simctl spawn "$SIMULATOR_UDID" defaults write com.apple.Preferences "$key" -bool false + done + # Suppress the slide-to-type intro overlay a fresh sim shows on first keyboard use + xcrun simctl spawn "$SIMULATOR_UDID" defaults write com.apple.keyboard.preferences \ + DidShowContinuousPathIntroduction -bool true + xcrun simctl spawn "$SIMULATOR_UDID" defaults read "Apple Global Domain" | grep Keyboard || true + + - name: Install app on simulator + run: | + echo "Installing $APP_PATH on $SIMULATOR_UDID" + xcrun simctl install "$SIMULATOR_UDID" "$APP_PATH" + + - name: Install Argent and start the tool-server + uses: ./.github/actions/argent-server + + - name: Run E2E tests + run: argent flow run ./.argent/flows --device $SIMULATOR_UDID + + # Interpreting the artifacts: + # - screen.png taken first, closest to the failure state (software keyboard + # still visible = the dismissal never completed). + # - probe-flow.txt distinguishes wedged from recovered: "ViewInspector RPC + # timed out" = the app STILL cannot serve a main-thread read minutes after + # the failure (true wedge); a plain "not visible" assert failure = the + # tree was readable again (bounded stall that recovered). + # - sample-app.txt runs WHILE the probe is in flight, so a wedged main + # thread is caught parked in the culprit frames. + # - spindump-system.txt covers the XPC peers (keyboard daemon, backboardd) + # in case main is waiting on another process. + - name: Capture app diagnostics + if: ${{ !success() }} + run: | + DIAG="$RUNNER_TEMP/diagnostics" + mkdir -p "$DIAG" + + xcrun simctl io "$SIMULATOR_UDID" screenshot "$DIAG/screen.png" || true + + BUNDLE_ID=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$APP_PATH/Info.plist") + PID=$(xcrun simctl spawn "$SIMULATOR_UDID" launchctl list 2>/dev/null \ + | awk -v label="UIKitApplication:$BUNDLE_ID" '$3 ~ label {print $1}' | head -1) + + # One-step throwaway flow: reads the UI tree through the same + # native-devtools path the real flows use. The assert is expected to + # fail either way; only the failure REASON matters (see above). + mkdir -p "$RUNNER_TEMP/probe-flow" + cat > "$RUNNER_TEMP/probe-flow/probe.yaml" <<'EOF' + steps: + - assert: { visible: "argent-diagnostic-probe-nonexistent" } + EOF + + if [ -n "$PID" ] && [ "$PID" != "-" ]; then + echo "Sampling $BUNDLE_ID (pid $PID) while probing the UI tree" + sample "$PID" 15 -file "$DIAG/sample-app.txt" & + SAMPLE_JOB=$! + argent flow run "$RUNNER_TEMP/probe-flow" --device "$SIMULATOR_UDID" \ + 2>&1 | tee "$DIAG/probe-flow.txt" || true + wait "$SAMPLE_JOB" || true + else + echo "$BUNDLE_ID is not running; nothing to sample" | tee "$DIAG/app-not-running.txt" + argent flow run "$RUNNER_TEMP/probe-flow" --device "$SIMULATOR_UDID" \ + 2>&1 | tee "$DIAG/probe-flow.txt" || true + fi + + sudo spindump -notarget 5 20 -file "$DIAG/spindump-system.txt" || true + + - name: Upload diagnostics + if: ${{ !success() }} + uses: actions/upload-artifact@v4 + with: + name: e2e-ios-diagnostics-${{ inputs.artifact-name }} + path: ${{ runner.temp }}/diagnostics/ + if-no-files-found: ignore + + # The tool-server log also carries the device backend's stderr + # (`[sim ]` lines), where a failed connection to the simulator is + # explained. + - name: Print Argent tool-server log + if: ${{ !success() }} + run: cat "$ARGENT_SERVER_LOG" 2>/dev/null || echo "no Argent tool-server log" diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index a40c21d9ac..8e6148ad5e 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -3,8 +3,11 @@ name: Build iOS (expo-example) on: pull_request: paths: + - .argent/flows/* + - .github/actions/argent-server/action.yml - .github/workflows/ios.yml - .github/workflows/ios-build.yml + - .github/workflows/ios-e2e.yml - packages/react-native-gesture-handler/package.json - packages/react-native-gesture-handler/RNGestureHandler.podspec - packages/react-native-gesture-handler/apple/** @@ -24,12 +27,39 @@ concurrency: cancel-in-progress: true jobs: + # Single source of truth for versions shared by the build and e2e workflows. + # A job output is the only way to reach them both: `jobs..with` resolves + # `needs`, `matrix` and `vars`, but not a workflow-level `env`. + config: + if: github.repository == 'software-mansion/react-native-gesture-handler' + + runs-on: ubuntu-latest + outputs: + xcode-version: ${{ steps.versions.outputs.xcode }} + + steps: + # The e2e job has to run the same Xcode the app was built with, so its + # simulator runtime matches the SDK. + - name: Pin versions + id: versions + run: echo "xcode=26.6" >> "$GITHUB_OUTPUT" + expo-example: if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: config uses: ./.github/workflows/ios-build.yml with: app: expo-example scheme: ExpoExample artifact-name: ios-app-expo-example - xcode-version: '26.4.1' + xcode-version: ${{ needs.config.outputs.xcode-version }} + + e2e: + if: github.repository == 'software-mansion/react-native-gesture-handler' + needs: [config, expo-example] + + uses: ./.github/workflows/ios-e2e.yml + with: + artifact-name: ios-app-expo-example + xcode-version: ${{ needs.config.outputs.xcode-version }} diff --git a/apps/common-app/src/common.tsx b/apps/common-app/src/common.tsx index fd12711d98..da9e92967d 100644 --- a/apps/common-app/src/common.tsx +++ b/apps/common-app/src/common.tsx @@ -144,17 +144,20 @@ type Props = { export function useIndexedLogger() { const messageCounter = useRef(0); - const logMessage = (message: string) => { + const logMessage = useCallback((message: string) => { messageCounter.current += 1; const indexedMessage = `${messageCounter.current}. ${message}`; console.log(indexedMessage); - }; + }, []); - const logMessageWorklet = (message: string) => { - 'worklet'; - // Schedule log on the JS thread so the console interceptor can pick it up - scheduleOnRN(logMessage, message); - }; + const logMessageWorklet = useCallback( + (message: string) => { + 'worklet'; + // Schedule log on the JS thread so the console interceptor can pick it up + scheduleOnRN(logMessage, message); + }, + [logMessage] + ); return logMessageWorklet; } diff --git a/apps/common-app/src/new_api/showcase/shared_value/index.tsx b/apps/common-app/src/new_api/showcase/shared_value/index.tsx index c9fa0c376a..4c069023a2 100644 --- a/apps/common-app/src/new_api/showcase/shared_value/index.tsx +++ b/apps/common-app/src/new_api/showcase/shared_value/index.tsx @@ -12,18 +12,29 @@ import Animated, { withTiming, } from 'react-native-reanimated'; -import { COLORS, commonStyles } from '../../../common'; +import { COLORS, commonStyles, useIndexedLogger } from '../../../common'; export default function SharedValueConfigExample() { const numberOfTaps = useSharedValue(1); const flashProgress = useSharedValue(0); + const log = useIndexedLogger(); const tap = useTapGesture({ numberOfTaps, + onBegin: () => { + log('onBegin'); + }, onActivate: () => { + log('onActivate'); flashProgress.value = 1; flashProgress.value = withTiming(0, { duration: 400 }); }, + onDeactivate: () => { + log('onDeactivate'); + }, + onFinalize: () => { + log('onFinalize'); + }, }); const boxStyle = useAnimatedStyle(() => ({ @@ -36,16 +47,30 @@ export default function SharedValueConfigExample() { return ( + + The button raises the number of taps the gesture requires. The gesture + reads it from a shared value, so the screen never re-renders. Open the + console to follow the gesture callbacks. + + - + { - numberOfTaps.value += 1; + // Reading `numberOfTaps.value` back right after the write still gives + // the old value, so the new one has to be kept in a local. + const requiredTaps = numberOfTaps.value + 1; + numberOfTaps.value = requiredTaps; + log(`Required taps: ${requiredTaps}`); }}> Increment required taps diff --git a/apps/common-app/src/new_api/simple/longPress/index.tsx b/apps/common-app/src/new_api/simple/longPress/index.tsx index c48aaa7455..7c42332a9a 100644 --- a/apps/common-app/src/new_api/simple/longPress/index.tsx +++ b/apps/common-app/src/new_api/simple/longPress/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Text, View } from 'react-native'; import { GestureDetector, @@ -18,6 +18,8 @@ export default function LongPressExample() { const log = useIndexedLogger(); const [count, setCount] = useState(0); + const incrementCount = useCallback(() => setCount((c) => c + 1), []); + const colorProgress = useSharedValue(0); const finalise_color = useSharedValue(COLORS.PURPLE); @@ -40,7 +42,7 @@ export default function LongPressExample() { }, onActivate: () => { log('onActivate'); - scheduleOnRN(setCount, count + 1); + scheduleOnRN(incrementCount); colorProgress.value = withTiming(2, { duration: 100, }); diff --git a/apps/common-app/src/new_api/simple/tap/index.tsx b/apps/common-app/src/new_api/simple/tap/index.tsx index 5fc04c75a2..a090cad9ba 100644 --- a/apps/common-app/src/new_api/simple/tap/index.tsx +++ b/apps/common-app/src/new_api/simple/tap/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useCallback, useState } from 'react'; import { Text, View } from 'react-native'; import { GestureDetector, useTapGesture } from 'react-native-gesture-handler'; import Animated, { @@ -16,6 +16,8 @@ export default function TapExample() { const colorProgress = useSharedValue(0); const log = useIndexedLogger(); + const incrementCount = useCallback(() => setCount((c) => c + 1), []); + const animatedStyle = useAnimatedStyle(() => { return { backgroundColor: interpolateColor( @@ -35,7 +37,7 @@ export default function TapExample() { }, onActivate: () => { log('onActivate'); - scheduleOnRN(setCount, count + 1); + scheduleOnRN(incrementCount); }, onDeactivate: () => { log('onDeactivate');