react-native-haptic-feedback

repository·main·Indexed 21 days ago

https://github.com/mkuczera/react-native-haptic-feedback

A comprehensive haptic feedback library for React Native (v0.71.0+) providing access to iOS Core Haptics, Android's Vibration API, and custom pattern playback via AHAP files. Features include predefined haptic types via trigger(), custom intensity via impact(), a TouchableHaptic wrapper for Pressable, and support for custom haptic sequences using pattern notation or AHAP files on iOS.

Tokens
28.4K
Snippets
100
Records
133
Agent score
75%

What's inside react-native-haptic-feedback

  1. How the global kill switch affects API methods

    main

    When setEnabled(false) is active, the following behaviors occur:

    MethodBehavior when disabled
    trigger()Returns immediately without firing
    triggerPattern()Returns immediately
    stop()Still runs (safe to call)
    isSupported()Returns device capability (unaffected by kill switch)
    playAHAP()Returns Promise.resolve() immediately
    playHaptic()Returns Promise.resolve() immediately

    If you are using the useHaptics hook, all calls through the returned object (e.g., haptics.trigger()) automatically respect this global state.

  2. The `enableVibrateFallback` option on iOS

    main

    The enableVibrateFallback option is a no-op on iOS because Core Haptics handles all supported devices internally. It is safe to include this option in your configuration to maintain cross-platform code compatibility.

    HapticFeedback.trigger("impactMedium", {
      enableVibrateFallback: true, // no-op on iOS — safe to pass for cross-platform code
    });
  3. Use Pattern Notation for compact haptic sequences

    main

    The pattern(string) helper allows you to build HapticEvent[] arrays from a compact string notation. This is useful for quick, readable pattern definitions.

    Notation Reference:

    CharacterMeaningTime advanceTotal O_O spacing
    oSoft transient (intensity 0.4, sharpness 0.4)100 ms
    OStrong transient (intensity 1.0, sharpness 0.8)100 ms
    .Short gap+150 ms250 ms
    -Medium gap+400 ms500 ms
    =Long gap+1000 ms1100 ms

    Validation:

    • Compile-time: TypeScript validates string literals passed to pattern().
    • Runtime: If a non-literal string is passed, pattern() throws a TypeError if invalid characters are present. Use PATTERN_CHARS to manually validate user input.
    import { pattern, PATTERN_CHARS } from "react-native-haptic-feedback";
    import type { PatternChar } from "react-native-haptic-feedback";
    
    // Play a pattern: soft, strong, 100ms pause, strong
    RNHapticFeedback.triggerPattern(pattern("oO.O"));
    
    // Programmatic validation
    const valid = [...input].every((c) => PATTERN_CHARS.has(c as PatternChar));
  4. How Android haptic fallback works

    main

    Android uses a two-tier fallback system:

    1. performHapticFeedback: Uses HapticFeedbackConstants via the activity's decorView. This respects the user's system haptic settings. This tier is skipped if ignoreAndroidSystemSettings: true is used.
    2. Vibrator API: Used if Tier 1 is skipped or unavailable. Uses VibrationEffect.createWaveform (API 26+) or raw waveforms (API 23+). API 31+ uses VibrationEffect.Composition for higher quality primitives.

    Android API Progression:

    • API 23: Minimum supported (raw waveform).
    • API 26: VibrationEffect.createWaveform with per-step amplitudes.
    • API 29: VibrationEffect.createPredefined for effect* types.
    • API 30: HapticFeedbackConstants for confirm, reject, gesture*, and segment* types.
    • API 31: VibrationEffect.Composition primitives.
    • API 33: VibrationAttributes.USAGE_TOUCH (respects system haptic preferences).
    • API 34: HapticFeedbackConstants for toggleOn, toggleOff, dragStart, gestureThreshold*, and noHaptics.
  5. Stability guarantee of useHaptics

    main

    The useHaptics hook returns a memoized object. It is designed to be stable, meaning the object reference only changes if the values within the defaultOptions object actually change. This prevents unnecessary re-renders or infinite loops when the haptics object is used as a dependency in React hooks like useEffect or useCallback.

    const haptics = useHaptics({ enableVibrateFallback: true });
    
    useEffect(() => {
      haptics.trigger("impactMedium");
    }, [haptics]); // ✓ stable — won't cause infinite loop
  6. View-based haptics on Android

    main

    The library uses decorView.performHapticFeedback() for precise, system-calibrated responses. This method accesses the activity's decorView directly without adding extra views to the layout tree.

    Always-available (API 23+)

    • clockTick
    • contextClick
    • keyboardPress
    • keyboardRelease
    • keyboardTap
    • longPress
    • textHandleMove
    • virtualKey
    • virtualKeyRelease

    API 30+ (Android 11)

    • confirm
    • reject
    • gestureStart
    • gestureEnd
    • segmentTick
    • segmentFrequentTick

    API 34+ (Android 14)

    • toggleOn
    • toggleOff
    • dragStart
    • gestureThresholdActivate
    • gestureThresholdDeactivate
    • noHaptics (explicit no-op)

    Note: On devices below the required API level, these types fall back to a VibrationEffect waveform equivalent.

  7. How to use AHAP files for complex haptics on iOS

    main

    Apple Haptic and Audio Pattern (AHAP) is a JSON-based format for defining complex haptic experiences. This feature is iOS-only. The library allows you to play .ahap files at runtime or compose AHAP objects directly in TypeScript.

    To use AHAP files, you must first include them in your iOS Xcode project as folder references so they are bundled with your app.

    import HapticFeedback from "react-native-haptic-feedback";
    
    // plays ios/YourApp/haptics/celebration.ahap
    await HapticFeedback.playAHAP("celebration.ahap");
  8. iOS Haptic Engine implementation details

    main

    On iOS, the library utilizes CHHapticEngine (Core Haptics) instead of UIKit feedback generators.

    Lifecycle Management:

    • The engine is created eagerly during module initialization to ensure the first haptic call has no latency.
    • If the engine is unavailable at initialization, it is created lazily on the first call.
    • The engine automatically restarts via its resetHandler; you do not need to manually manage its lifecycle.

    Requirements:

    • Minimum iOS version: 13.0 (required for Core Haptics). While the library compiles for older targets, no haptic output will be produced on devices running versions below 13.0.
  9. How iOS haptic fallback works

    main

    On iOS, every trigger() call follows a three-tier fallback chain. The system stops at the first tier that is supported by the device hardware/software:

    1. Core Haptics: Requires iPhone 8+ or iPad Pro (iOS 13+). Provides full per-type patterns with custom intensity and sharpness using CHHapticEngine.
    2. UIKit generators: Requires a Taptic Engine (iPhone 6s, 7, SE 1st gen on iOS 13+). Uses UIImpactFeedbackGenerator, UINotificationFeedbackGenerator, or UISelectionFeedbackGenerator to semantically map types to system haptics.
    3. Audio vibration: Available on any device. Uses AudioServicesPlaySystemSound(kSystemSoundID_Vibrate). Note: This only fires if enableVibrateFallback: true is set in your options.

    Tier 3 is intended for devices without a Taptic Engine (e.g., iPod touch 7th gen).

  10. New cross-platform utilities in v3

    main

    The following utilities are available for cross-platform haptic implementation:

    • playHaptic(ahapFile, fallback, options?): Plays an AHAP file on iOS, or falls back to a pattern on Android.
    • pattern(notation): Converts a notation string into a HapticEvent[] array.
    • Patterns: A collection of six built-in named presets.
    • useHaptics(defaultOptions?): A React hook for integrating haptics into components.
    • TouchableHaptic: A Pressable wrapper component for easy haptic integration.
  11. Behavior differences between Mobile and Web

    main

    Haptic behavior varies significantly between mobile platforms and the web due to the limitations of the Web Vibration API:

    | Feature          | Mobile                            | Web                                             |
    | ---------------- | --------------------------------- | ----------------------------------------------- |
    | `trigger()`      | System-calibrated haptic per type | Fixed-duration `navigator.vibrate(ms)` per type |
    | `impact()`       | Custom CHHapticEngine / amplitude | Fixed duration (intensity ignored by Web API) |
    | `triggerPattern` | CHHapticEngine / VibrationEffect  | `navigator.vibrate([gap, dur, gap, dur, …])`  |
    | `stop()`         | Cancels CHHapticEngine / Vibrator | `navigator.vibrate(0)`                          |
    | `isSupported()`  | Checks hardware / CHHapticEngine  | `'vibrate' in navigator`                        |
    | `playAHAP()`     | iOS only                          | Resolves immediately (no-op)                    |
  12. Handle intensity differences on Web

    main

    Because the Web Vibration API does not support vibration amplitude, the impact() method uses a fixed duration on web and ignores the intensity parameter. If precise intensity is critical to your UX, you should gate your calls by checking the platform.

    import { Platform } from "react-native";
    import { impact } from "react-native-haptic-feedback";
    
    if (Platform.OS !== "web") {
      impact("impactMedium", 0.3); // precise on iOS / Android
    } else {
      impact("impactMedium"); // web: fixed duration, intensity ignored
    }