Haptics in React Native: Creating a useHaptic() Hook

Karthik Balasubramanian
Timeless

--

Hey, Fellow React Native Developers!

I think you would have used an app that gave you a really satisfying vibration or haptic feedback when you interacted with any Tappable Components, like when you pressed a button or completed a task or something.

Haptic feedback adds a lot to the UX of an app. Adding haptics into your app has become really easy with a couple of libraries out there.

And another one is from Expo.

In this blog post, we are going to use the expo-haptics and we’ll be seeing how to create a common custom React hook, useHaptic() that can handle the haptic feedback throughout your app.

I hope by the end of this blog, you will have a clear understanding of how to create a common functionality as hooks. You can check out a similar blog on “Creating a common Scale Animation Handler Hook”

Well, let us dive in and create our hook!

Cover Image by Fayas fs

We will be using expo-haptics.

It works on all platforms except on the Web.

Installation

npx expo install expo-haptics

If you are installing a bare React Native App, you need to go through some additional installation steps. You can check it out here.

General Usage

import * as Haptics from 'expo-haptics';

// In component
<Button
title="Success"
onPress={
() =>
Haptics.notificationAsync(
Haptics.NotificationFeedbackType.Success
)
}
/>

Creating the Hook

Defining Required Types for the Hook

From the docs, you can see there are about seven Feedback types for the Haptic. So that will be our first type.

type FeedbackType =
| "light"
| "medium"
| "heavy"
| "selection"
| "success"
| "warning"
| "error";

And these types are grouped mainly into two (well three) types:

  1. ImpactFeedbackStyle
    - light
    - medium
    - heavy
  2. NotificationFeedbackType
    - success
    - warning
    - error
  3. Selection
    - selection

We will create separate handlers for the ImpactFeedbackStyle & NotificationFeedbackType.

Both handlers will be very similar.

const createHapticHandler = useCallback(
(type: Haptics.ImpactFeedbackStyle) => {
return Platform.OS === "web"
? undefined
: () => Haptics.impactAsync(type);
},
[],
);
const createNotificationFeedback = useCallback(
(type: Haptics.NotificationFeedbackType) => {
return Platform.OS === "web"
? undefined
: () => Haptics.notificationAsync(type);
},
[],
);

Both the handler's returns undefined which means that the function doesn't do anything, because we don't have support for the web.

It returns the actual expo-haptics respective function, like for ImpactFeedbackStyle its impactAsync() and for NotificationFeedbackType it is notificationAsync()with the type.

These functions are created using the useCallback to make sure that they are only created only once during the component’s lifecycle and are not recreated unnecessarily on each render.

Using the above-created handlers we create an object mapping each haptic feedback type to its respective handler functions.

We create this object using useMemo() that ensures it is only created once during the component’s lifecycle.

const hapticHandlers = useMemo(
() => ({
light: createHapticHandler(Haptics.ImpactFeedbackStyle.Light),
medium: createHapticHandler(Haptics.ImpactFeedbackStyle.Medium),
heavy: createHapticHandler(Haptics.ImpactFeedbackStyle.Heavy),
selection: Platform.OS === "web" ? undefined : Haptics.selectionAsync,
success: createNotificationFeedback(
Haptics.NotificationFeedbackType.Success,
),
warning: createNotificationFeedback(
Haptics.NotificationFeedbackType.Warning,
),
error: createNotificationFeedback(Haptics.NotificationFeedbackType.Error),
}),
[createHapticHandler, createNotificationFeedback],
);

The object’s properties are:

  1. light, medium, and heavy: These are mapped to the createHapticHandler() one we have already created, corresponding to the ImapactFeedbackStyle. This is to handle haptic for impacts of different strengths.
  2. selection: Mapeped to Haptics.selectionAsync() and undefined if the Platform is Web.
  3. success, warning, error: These are mapped to the createNotificationFeedback() corresponding to NotificationFeedbackType. This is to handle haptic for different types of notifications.

Let us now piece it all together to get one useHaptic().

import { useCallback, useMemo } from "react";
import { Platform } from "react-native";
import * as Haptics from "expo-haptics";

type FeedbackType =
| "light"
| "medium"
| "heavy"
| "selection"
| "success"
| "warning"
| "error";

export const useHaptic = (feedbackType: FeedbackType = "selection") => {
const createHapticHandler = useCallback(
(type: Haptics.ImpactFeedbackStyle) => {
return Platform.OS === "web"
? undefined
: () => Haptics.impactAsync(type);
},
[],
);
const createNotificationFeedback = useCallback(
(type: Haptics.NotificationFeedbackType) => {
return Platform.OS === "web"
? undefined
: () => Haptics.notificationAsync(type);
},
[],
);

const hapticHandlers = useMemo(
() => ({
light: createHapticHandler(Haptics.ImpactFeedbackStyle.Light),
medium: createHapticHandler(Haptics.ImpactFeedbackStyle.Medium),
heavy: createHapticHandler(Haptics.ImpactFeedbackStyle.Heavy),
selection: Platform.OS === "web" ? undefined : Haptics.selectionAsync,
success: createNotificationFeedback(
Haptics.NotificationFeedbackType.Success,
),
warning: createNotificationFeedback(
Haptics.NotificationFeedbackType.Warning,
),
error: createNotificationFeedback(Haptics.NotificationFeedbackType.Error),
}),
[createHapticHandler, createNotificationFeedback],
);

return hapticHandlers[feedbackType];
};

Well, this is our hook! Locked and loaded to be used inside our components.

We can consume our hook in different ways based on our use case.

If we need our haptic to let a user know when a selection change has been registered. We can use the default selection.

const hapticSelection = useHaptic();

// In component onPress
hapticSelection()

The hook defaults to selection and returns Haptics.selectionAsync().

You can customize it to give a specific feedback type by passing in any one of the FeedbackType.

Various Feeback Options of the hook

Well, this brings us to the end of this blog.

With the above-created hook, we can now easily access and provide haptic feedback to the user throughout the app, without having to write repetitive code.

I hope you found this blog resourceful and that it improves your development process in some way.

Check out my blogs on popular UI Interactions with React Native here.

UI Interactions With React Native and Reanimated

26 stories

I am trying to be more consistent to write more about working with Reanimated and Gesture Handler Libraries to achieve said UI Interactions. Follow and stay tuned!

This is Karthik from Timeless

If you find this blog post helpful, tell me in the comments, would love to know your views. Thank you :)

If you find any difficulties or see anything which could be done better, please feel free to add your comments (I really like to read them, maybe something on what my next blog should be? You know. Whatev! ). … :)

--

--