React Native
React Native Gesture Handler's Touchable: The Button We Wish We Had Sooner
Jakub PiaseckiJakub PiaseckiAug 20, 20264 min read

I like buttons. I think most people like buttons, especially when they give satisfying feedback when pressed. So you need a lot of them, with different kinds of feedback, right? Well, not really –  that’s how you end up with multiple components doing the same thing, but slightly differently. And we learned it the hard way, during React Native Gesture Handler’s years-long development.

How did we get here?

When you install a library for handling touch in your app, you expect it to have a ready-to-use button component. And we did have a few of them, each with different visual feedback. As React Native evolved, there came a need for drop-in replacements for its Touchable components. Then React Native got Pressable, and we needed a drop-in for that as well. Suddenly, we were exporting 9 separate button components.

We realized this when consulting a large financial app about button usage. After a lot of discussions about the expectations and assumptions, we decided that there’s no one-size-fits-all preset here. But we can share the tools to fit a vast majority of use cases, so we decided to solve this once and for all.

Don’t reinvent the Pressable, we already did it for you

Obviously, the solution to “there are too many buttons” is one button to rule them all. We did that in React Native Gesture Handler 3 with Touchable, our new button component. Now that we have one more button component, the situation is clear (we did deprecate the other ones). One exception is our Pressable – it became a small wrapper around Touchable to preserve a drop-in replacement in cases where a gesture-aware button is needed.

How is it better?

First of all, it’s customizable, allowing you to set a custom scale, opacity, and underlay for each of the states: default, pressed, and hovered. Oh, it also supports hover on mobile (both with mouse and pencils). Additionally, you can set custom transition times for each of the states separately. All of that lives at the platform level (ObjectAnimator on Android, CoreAnimation on iOS, and CSS transitions on the web), which also respects the OS accessibility settings like animation scale and reduce motion.

Opacity:

<Touchable
 style={styles.button}
 activeOpacity={0.7}
 onPress={onPress}>
 <Text style={styles.text}>activeOpacity: 0.7</Text>
</Touchable>

Scale:

<Touchable
 style={styles.button}
 activeScale={1.05}
 onPress={onPress}>
 <Text style={styles.text}>activeScale: 1.05</Text>
</Touchable>

Underlay:

<Touchable
 style={styles.button}
 underlayColor="black"
 onPress={onPress}>
 <Text style={styles.text}>underlayColor: black</Text>
</Touchable>

The JS component is basically a simple wrapper over the codegen spec. If you rely on the built-in animations, Touchable doesn’t re-render by itself.

If you need more configuration options, you can use your favourite animation library to drive the transitions yourself, just like with React Native’s Pressable.

I heard you like numbers

We prepared them for you – 50 runs of 1000 Buttons rendered at once, lower is better.

Absolute values (in milliseconds)

ComponentOppo A16 meanOppo A16 medianPixel 9 Pro meanPixel 9 Pro median
Touchable7838.47737.0574.0573.0
Pressable8180.38197.0651.0652.0
RectButton8281.07995.01067.91057.0

Relative values (compared to Pressable)

ComponentOppo A16 meanOppo A16 medianPixel 9 Pro meanPixel 9 Pro median
Touchable0.960.940.880.88
Pressable1.001.001.001.00
RectButton1.010.981.641.62

The new Touchable component is faster to render than the old RectButton, and scores a win against React Native’s built-in Pressable as well. The differences are most pronounced on more powerful devices, which aren’t overwhelmed by the JavaScript workload.

Why Touchable?

Pressable was already taken ¯\_(ツ)_/¯… oh, “when would I use this”, not “why did you call it that”, got it. Currently, there are three ways to handle tap interaction in Gesture Handler: useTapGesture, Touchable, and Pressable. In the vast majority of cases, Touchable should be the default choice, and the decision on which one to use should be much simpler now:

  • Use useTapGesture when you need to customize the behavior, like the number of taps, maximum tap duration, or you need more complex relations between different gestures.

  • Use Pressable, a Touchable in a trenchcoat, when you have existing components using React Native’s Pressable, and you want to migrate to Gesture Handler using a drop-in replacement.

  • Otherwise, just use Touchable.

What about migrating?

We plan to remove the deprecated buttons in the next major release of Gesture Handler, so we strongly recommend using Touchable instead. You can migrate manually, or leave that to your agent of choice using our migration skill.
 

Share this article