React-native-web: Hover support in Pressable

Created on 20 Aug 2020  路  36Comments  路  Source: necolas/react-native-web

Is your feature request related to a problem? Please describe.

I would like it to make the hover status part of react-native-web since it's an important interaction of the web UX.

e.g.

<Pressable
        onPress={() => {
          setTimesPressed((current) => current + 1);
        }}
        style={({ pressed, hovered, focused }) => [
          {
            backgroundColor: pressed
              ? 'rgb(210, 230, 255)'
              : 'white'
          },
          styles.wrapperCustom
        ]}>
        {({ pressed, hovered, focused }) => (
          <Text style={styles.text}>
            {pressed ? 'Pressed!' : 'Press Me'}
          </Text>
        )}
      </Pressable>

Describe a solution you'd like

To address these problems, we are shipping a new core component called Pressable. This component can be used to detect various types of interactions. The API was designed to provide direct access to the current state of interaction without having to maintain state manually in a parent component. It was also designed to enable platforms to extend it's capabilities to include hover, blur, focus, and more. We expect that most people will build and share components utilizing Pressable under the hood instead of relying on the default experience of something like TouchableOpacity.

https://reactnative.dev/blog/2020/07/06/version-0.63

Additional context

Most helpful comment

Yeah I worked on this API at FB, but the hover implementation I originally wrote for Pressable isn't as good as the one I've subsequently written for another framework. So I need to take a bit of time to port it over here and reorganize this project to accommodate it first.

All 36 comments

Awesome feature. We enable Managing to hover easily if this is available.

Yeah I worked on this API at FB, but the hover implementation I originally wrote for Pressable isn't as good as the one I've subsequently written for another framework. So I need to take a bit of time to port it over here and reorganize this project to accommodate it first.

Would you accept a PR in the meantime which creates the hovered state with the onMouseIn onMouseOut the same way the focus is handled at the moment?

https://github.com/necolas/react-native-web/blob/cfe36d780e981885ce73862a9effcea29e7ea1c9/packages/react-native-web/src/exports/Pressable/index.js#L139

E.g. createHoverHandler

   onMouseIn={createHoverHandler(onMouseIn, true)}
   onMouseOut={createHoverHandler(onMouseOut, false)}

No, because the behavior would be incorrect

Ah, when would this incorrect behaviour trigger?

Any time a touch interaction occurs or a pressable is nested

Ah indeed maybe we could add some kind of tag to the event that it is being handled by the hover but that would be much more complicated that I would have imagined 馃槄.

Proof of concept: https://codesandbox.io/s/great-goldwasser-mduf1?from-embed=&file=/src/App.js
Mobile link: https://mduf1.csb.app/

[x] Nested pressables
[x] Touch interaction
[x] Works inside scrollviews
[x] Works inside nested scrollviews

I've got touch support working too

I improved the touch handling on touch devices by only listening to onMouseOver on devices which have a mouse to prevent the element from hovering infinite if it's clicked

One thing which could be improved is the array with refs, ideally this should be a shorted lookup based on some kind of target id or the reference of the target/ref but that's not possible as far as I know.

This is harder, than I thought because also the elements inside the clickable may not receive pointer events for this to work...

It also work with nested children inside a clickable element now

Mobile link: https://mduf1.csb.app/

This does not work yet together with FlatList we would need some kind of listeners to all overflow:y divs. Won't work well together if user has it's own virtualized lists

Works inside scrollview now too ;)

I get hover effects on every touch interaction on my phone in that demo.

And the approach to ignore touch wouldn't work properly on devices that support both mouse and touch interactions.

But it's ok we already have a solution that I'll integrate here at some point

"And the approach to ignore touch wouldn't work properly on devices that support both mouse and touch interactions."

It does because I don't check on touch support but on mouse support. so it'll work on devices who have both touch and mouse. I only disable the mouseOver events on devices which don't have a mouse to fix hover on touch devices.

I get hover effects on every touch interaction on my phone in that demo.

The touch behaviour could be improved. I don't know what should be desired for the hover effects on mobile.

But it's ok we already have a solution that I'll integrate here at some point

Great, looking forward too it!

It does because I don't check on touch support but on mouse support

Yeah, and as a result, on those devices touch interactions trigger hover effects.

You're totally right! Forgot about that one :)

the hover implementation I originally wrote for Pressable isn't as good as the one I've subsequently written for another framework.

@necolas is the implementation you made for the other framework open source by chance? I know it'll take a while to organize and add it to this library, so I figured I'd ask in the meantime. Thanks for the great work.

Maybe we can create a small library in the meantime which wraps react-native-web Pressable and scrollviews with hover support

E.g.

import { ScrollView, Pressable } from 'react-native-web-hover'

Update: I've made a library to support hover in the meantime
https://codesandbox.io/s/young-surf-bbmzz?file=/src/App.tsx

import { Pressable, ScrollView } from "react-native-web-hover";
.....

    <Pressable
      style={({ hovered, focused, pressed }) => [
        styles.buttonRoot,
        hovered && styles.buttonHovered,
        focused && styles.buttonFocused,
        pressed && styles.buttonPressed
      ]}
    >
      {({ hovered, focused, pressed }) => (
        <View style={styles.buttonInner}>
          <Text style={styles.buttonLabel}>
            {label}
          </Text>
        </View>
      )}
    </Pressable>

https://github.com/web-ridge/react-native-web-hover

yarn add react-native-web-hover

Update: I've created a library to support hovering with react-native-web

https://github.com/web-ridge/react-native-web-hover

I already pointed out the problems with that implementation. And I'd rather people didn't use this project's issue tracker to promote their packages. Thanks

The issues you pointed out are fixed now in the library.

document.ontouchstart = unhover;
document.ontouchend = unhover;
document.ontouchcancel = unhover;
document.ontouchmove = unhover;

I would rather not have this package too, but it is a temporary workaround for some people who need this feature. I'm sorry if you feel that way. I just wanted to help you and I like react-native-web very much!

Hover will be added in 0.14 which can be tested by installing the @canary version

Amazing work <3! I will deprecate the library

Hover will be added in 0.14 which can be tested by installing the @canary version

How can I achieve the same thing in non-web? I'd like to replicate the same behavior in both places, but I'm clearly missing some key words in my Google searches.

What platforms are you referring to? React Native still needs to implement this on iOS.
https://react-native.canny.io/feature-requests/p/ios-pointer-support

The macOS, Windows needs to add this too to the Pressable component. I think you need to make an issue on those platforms.
I think react-native-web is the only one to support this at the moment.

What platforms are you referring to? React Native still needs to implement this on iOS.
https://react-native.canny.io/feature-requests/p/ios-pointer-support

The macOS, Windows needs to add this too to the Pressable component. I think you need to make an issue on those platforms.
I think react-native-web is the only wants to support this at the moment.

It just seems iOS/Android at least, don't have any kind of hover. I'm using pan responder for dragging but there seems to be no way to detect what you're dropping on to without comparing the positions of every component

TypeScript typing still doesn't recognize "hovered", would it be necessary to open an issue in the @type/react-native project?

TypeScript typing still doesn't recognize "hovered", would it be necessary to open an issue in the @type/react-native project?

I think that would be great.
If they are not ok with this, we can create another type definitions module which expands @type/react-native for the web too.

@rodrigojcmello There's discussion about this on #1684, you can see my comment there.

This is a pretty lightweight setup to make Typescript happy

export type PressableState = Readonly<{
  pressed: boolean;
  hovered?: boolean;
  focused?: boolean;
}>;

function Button(): ReactElement {
  return (
    <Pressable>
      {({ pressed, hovered, focused }: PressableState): ReactElement => {
        return (
          <ButtonBase color={pressed ? "black" : focused ? "gray" : hovered ? "light gray" : "white"} />
        );
      }}
    </Pressable>
  );
}

By explicitly defining the input type of the function this way, it still makes sense to pass it to Pressable which expects a function with an input of Readonly<{ pressed: boolean }> and inside the function you can just treat the values as truthy or falsy.
Also if you're building tri-platform, these values actually will be undefined on mobile so the type is robust to this.

Was this page helpful?
0 / 5 - 0 ratings