microsoft / react-native-windows

A framework for building native Windows apps with React.
https://microsoft.github.io/react-native-windows/
Other
16.24k stars 1.14k forks source link

[0.74] Flyout does not display when target prop reference has been disabled #13259

Open debdalc opened 3 months ago

debdalc commented 3 months ago

Problem Description

I'm working on a react-native-windows project which was just recently updated to 0.74 from 0.66. After updating we had some issues arise with our custom dropdown component. In this component, a Touchable Opacity acts as the dropdown button and a Flyout is rendered to display the list of options in the dropdown underneath the button, using the Touchable Opacity reference for the target prop.

We disable the dropdown by disabling the touchable opacity. When the component is enabled again, the flyout stops rendering in unless it is forced to re-draw. This behaviour was not present before updating to 0.74.

I've provided a reproducible example which also shows that this issue is also seen with Pressable. It seems any target reference that is disabled atleast once, will prevent the Flyout from displaying with that target. A video demo of this example can be viewed below:

https://github.com/microsoft/react-native-windows/assets/142518306/7469c5fc-3327-459d-a078-b4e1b5a4e282

Steps To Reproduce

  1. In the demo app, pressing the two buttons should bring up their respective Flyouts at first
  2. When you press the 'enable/disable' button, it changes the state isDisabled which is passed into the disabled prop of both the buttons.
  3. After you disable and re-enable the buttons, the flyout is no longer rendered in.

Current Workaround for us

We found that passing the reference object of the View component within Touchable Opacity or Pressable to the target prop in Flyout gets around the issue for now, so this issue isn't blocker for our project

CLI version

13.6.6

Environment

System:
  OS: Windows 10 10.0.19045
  CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
  Memory: 11.31 GB / 31.69 GB
Binaries:
  Node:
    version: 18.20.2
    path: C:\Program Files\nodejs\node.EXE
  Yarn:
    version: 3.6.4
    path: C:\Program Files (x86)\Yarn\bin\yarn.CMD
  npm:
    version: 10.5.0
    path: C:\Program Files\nodejs\npm.CMD
  Watchman: Not Found
SDKs:
  Android SDK: Not Found
  Windows SDK:
    AllowDevelopmentWithoutDevLicense: Enabled
    AllowAllTrustedApps: Enabled
    Versions:
      - 10.0.18362.0
      - 10.0.19041.0
      - 10.0.22621.0
IDEs:
  Android Studio: AI-222.4459.24.2221.10121639
  Visual Studio:
    - 17.9.34728.123 (Visual Studio Professional 2022)
Languages:
  Java: Not Found
  Ruby:
    version: 2.7.0
    path: C:\Program Files\ServiceNow\agent-client-collector\embedded\bin\ruby.EXE
npmPackages:
  "@react-native-community/cli": Not Found
  react:
    installed: 18.3.1
    wanted: 18.3.1
  react-native:
    installed: 0.74.1
    wanted: 0.74.1
  react-native-windows:
    installed: 0.74.4
    wanted: 0.74.4
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: Not found
  newArchEnabled: Not found

Target Device(s)

Desktop

Visual Studio Version

Visual Studio 2022

Build Configuration

Debug

Snack, code example, screenshot, or link to a repository

Code Snippet from Demo App

import React, { useRef, useState } from 'react';
import {
  Button,
  Pressable,
  SafeAreaView,
  StyleSheet,
  Text,
  TouchableOpacity,
  View,
} from 'react-native';
import { Flyout } from 'react-native-windows';

function App(): React.JSX.Element {

  const pressableRef = useRef(null);
  const touchableOpacityRef = useRef(null);
  const [expanded1, setExpanded1] = useState(false);
  const [expanded2, setExpanded2] = useState(false);

  const [isDisabled, setIsDisabled] = useState(false);

  return (
    <SafeAreaView style={styles.container}>
      <View style={{flexDirection: 'row'}}>
        <Pressable
          onPress={() => setExpanded1(!expanded1)}
          disabled={isDisabled}
          ref={pressableRef}>
          <View style={[styles.dropdownButton, {opacity: isDisabled ? 0.2 : 0.7}]}>
            <Text>Pressable Button</Text>
          </View>
          <Flyout
            isOpen={expanded1}
            onDismiss={() => setExpanded1(false)}
            target={pressableRef.current}
            placement={'bottom-edge-aligned-left'}>
            <View style={styles.dropdown}>
              <Text>This flyout is triggered by the Pressable Component</Text>
            </View>
          </Flyout>
        </Pressable>
        <TouchableOpacity
          onPress={() => setExpanded2(!expanded2)}
          disabled={isDisabled}
          ref={touchableOpacityRef}>
          <View style={[styles.dropdownButton, {opacity: isDisabled ? 0.2 : 0.7}]}>
            <Text>Touchable Opacity</Text>
          </View>
          <Flyout
            isOpen={expanded2}
            onDismiss={() => setExpanded2(false)}
            target={touchableOpacityRef.current}
            placement={'bottom-edge-aligned-left'}>
            <View style={styles.dropdown}>
              <Text>This flyout is triggered by the Touchable Opacity Component</Text>
            </View>
          </Flyout>
        </TouchableOpacity>
      </View>
      <View style={{paddingLeft: 15}}>
        <Button
          title='enable/disable'
          onPress={() => setIsDisabled(!isDisabled)}
        />
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    marginTop: 250,
    paddingHorizontal: 24,
    flexDirection: 'row',
    alignSelf: 'center',
    alignItems: 'center',
    justifyContent: 'center',
  },
  dropdownButton : {
    height: 50,
    width: 100,
    alignSelf: 'center',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: 'grey',
    marginHorizontal: 25
  },
  dropdown : {
    height: 200,
    width: 400,
    alignSelf: 'center',
    alignItems: 'center',
    justifyContent: 'center'
  }
});

export default App;
chrisglein commented 3 months ago

Trying to zero in on where the failure might be happening here. Could be in the pressable/touchable disabled, could be in the flyout, could be in the ref.

Is the issue that the Flyout shows but doesn't have the target ref (due to some bug with that going enabled->disabled->enabled) and thus doesn't show? As in does this work if target is unset and your Flyout shows floating without that alignment?

Not saying that's what you should do, just trying to narrow down the issue. There's a lot that's happened between 0.66 and 0.74 ;) (including specifically changes to enabled/disabled handling)

TatianaKapos commented 3 months ago

I'm seeing this issue in 73!

In 72 the pressable button works fine but the touchable opacity does not.

debdalc commented 3 months ago

Is the issue that the Flyout shows but doesn't have the target ref (due to some bug with that going enabled->disabled->enabled) and thus doesn't show? As in does this work if target is unset and your Flyout shows floating without that alignment?

@chrisglein Yes, removing the target prop from the flyout does make it display at a random location on the screen, in our actual project it was appearing at the top left corner. In the demo app as can be seen from the video below it appears at the bottom left corner.

https://github.com/microsoft/react-native-windows/assets/142518306/aedcc4d4-1557-45c9-bfef-8c7cf69092f8