facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
118.97k stars 24.31k forks source link

Add support for Paste, Copy, Cut events in TextInput #18926

Closed pronebird closed 4 years ago

pronebird commented 6 years ago

TextInput does not support Paste, Copy, Cut events, but it should.

Environment

Environment: OS: macOS High Sierra 10.13.4 Node: 8.10.0 Yarn: 1.5.1 npm: 5.6.0 Watchman: Not Found Xcode: Xcode 9.3 Build version 9E145 Android Studio: Not Found

Packages: (wanted => installed) react: ^16.0.0 => 16.2.0 react-native: ^0.53.3 => 0.53.3

Steps to Reproduce

Not applicable.

Expected Behavior

Not applicable.

Actual Behavior

Not applicable.

react-native-bot commented 6 years ago

Thanks for posting this! It looks like your issue may be missing some necessary information. Can you run react-native info and edit your issue to include these results under the Environment section?

Thank you for your contributions.

react-native-bot commented 6 years ago

Thanks for posting this! It looks like your issue may refer to an older version of React Native. Can you reproduce the issue on the latest release, v0.55?

Thank you for your contributions.

react-native-bot commented 6 years ago

Thanks for posting this! It looks like your issue may refer to an older version of React Native. Can you reproduce the issue on the latest release, v0.55?

Thank you for your contributions.

react-native-bot commented 6 years ago

It looks like your issue may refer to an older version of React Native. Can you reproduce the issue on the latest release, v0.55?

pronebird commented 6 years ago

@react-native-bot I can't see any changes related to copy/paste or cut events in the most recent react-native.

n0213004 commented 6 years ago

I'm having the same problem. My users are complaining about not being able to use their password managers to paste passwords into TextInput. I haven't been able to find an elegant way around this.

I have found that the pasting limitation seems to be a Rich Text vs. Plain Text scenario. These steps will result in a failed paste:

  1. Open the Notes app on your iPhone
  2. Type and then copy some text
  3. Paste this into your TextInput

While I've found that these steps will result in a successful paste

  1. Open Safari
  2. Navigate to any website
  3. Select and copy the website URL
  4. Paste this into your TextInput

Hopefully this can be address in an upcoming release.

Environment: OS: macOS High Sierra 10.13.5 Node: 8.2.1 Yarn: 1.7.0 npm: 6.1.0 Watchman: 4.7.0 Xcode: Xcode 9.4.1 Build version 9F2000 Android Studio: 3.1 AI-173.4819257

Packages: (wanted => installed) react: 16.0.0 => 16.0.0 react-native: 0.50.4 => 0.50.4

markmckimlit commented 6 years ago

Hi, I'm also impacted by this. Getting a lot of negative App Store reviews by customers unable to use LastPass and OnePassword with our app.

harryhiggins commented 6 years ago

I'm hitting this issue as well. This is a pretty popular method of entering passwords, would love RN support.

trinhdan5555 commented 6 years ago

I am having the same problem as well. It it very frustrated

chrismowbraylit commented 6 years ago

Also having this issue

conor-lit commented 6 years ago

Customers reporting the same issue.

christophermark commented 6 years ago

Copy/Paste/Select in TextInputs works, but seems to be broken when the TextInput is in a particular UI: https://github.com/facebook/react-native/issues/9958

njho commented 6 years ago

Seems like Paste functionality works on android, and iPhone 8. In particular, doesnt seem to be working on iPhone X for myself.

Haven't tested extensively across multiple devices.

memken commented 6 years ago

Handoff issue? Finding that having Handoff activated on iPhone/iPad disallows paste within react-native TextInput. We've shown that disabling Handoff on iOS devices then allows paste to work.

(On iOS > Settings > General > Handoff.)

harryhiggins commented 6 years ago

I tested this again disabling Handoff on iPhone X (iOS 12 beta) and the problem still exists.

memken commented 6 years ago

@harryhiggins . Thanks for testing that out! I forgot to mention that sometimes it requires a restart of the iPhone/iPad for it to work as well. It's very flaky, sometimes it works, sometimes not. We've also found that turning off App Refresh and restarting phone will allow it to work as well. Here are a couple of articles which speak to both these issues:

https://www.osxiosexpert.com/universal-clipboard-not-working-here-is-how-to-fix-it/ https://discussions.apple.com/thread/8091217

Hard to know what may be going on here. Is it a react-native issue or iOS (both)?

nmhoan76 commented 6 years ago

have any some update?

larstadema commented 5 years ago

Encountered this issue as well.

We use expo, so react-native version is slightly out of date:

Environment:
  OS: macOS High Sierra 10.13.4
  Node: 8.12.0
  Yarn: 1.12.3
  npm: 6.4.1
  Watchman: 4.9.0
  Xcode: Xcode 9.4 Build version 9F1027a
  Android Studio: 3.1 AI-173.4720617

Packages: (wanted => installed)
  react: 16.3.1 => 16.3.1
  react-native: https://github.com/expo/react-native/archive/sdk-30.0.0.tar.gz => 0.55.4

Seems to happen when a <TextInput /> is inside a <KeyboardAvoidingView />, like https://github.com/facebook/react-native/issues/9958 described.

For us what fixed it (removed noise, only the relevant changes):

import React, { Component } from 'react';
import { TextInput as NativeTextInput } from 'react-native';

class TextInput extends Component {
  state = {
     inputWidth: '99%'
  }

  componentDidMount() {
    setTimeout(() => this.setState({ inputWidth: 'auto' }), 100);
  }

  render() {
    const { inputWidth } = this.state;

    return <NativeTextInput style={{ width: inputWidth }} />
 }
}
devpascoe commented 5 years ago

hmmm appreciate the idea @larstadema however when i create my own TextInput component using your code and use it instead of the RN one i get this error "Warning: Can't call setState (or forceUpdate) on an unmounted component." Likely to do with the setTimeout.

Back to the drawing board.

devpascoe commented 5 years ago

I'm back from the drawing board :) setting an isMounted var worked well. I did also have to change TextInput as NativeTextInput back to TextInput and rename the component something like SafeTextInput to avoid naming conflict. So far so good.

componentDidMount() { this._isMounted = true setTimeout(() => { if (this._isMounted) { this.setState({ inputWidth: 'auto' }) } }, 100) }

componentWillUnmount() { this._isMounted = false }

devpascoe commented 5 years ago

and for fun i'll share my render. I'm using lodash for its cloneDeep method. Its probably an expensive render method but it gets the job done passing in all props from the component above it.

render() { const { inputWidth } = this.state const combinedStyle = .cloneDeep(StyleSheet.flatten(this.props.style ? this.props.style : {})) combinedStyle['width'] = inputWidth const remainingProps = .cloneDeep(this.props) remainingProps['style'] = combinedStyle return <TextInput {...remainingProps} /> }

devpascoe commented 5 years ago

better yet make the width change on demand, i intercept and pass along the onFocus event which is less taxing than a setTimeout (especially since i load a bunch of them in a list eg, list of posts with comment boxes) ...

import React, { Component } from 'react'
import { TextInput, StyleSheet } from 'react-native'
import _ from 'lodash'

class SafeTextInput extends Component {
  state = {
    inputWidth: '99%'
  }

  render() {
    const { inputWidth } = this.state
    const combinedStyle = _.cloneDeep(StyleSheet.flatten(this.props.style ? this.props.style : {}))
    combinedStyle['width'] = inputWidth
    const remainingProps = _.cloneDeep(this.props)
    remainingProps['style'] = combinedStyle
    return <TextInput {...remainingProps} onFocus={this.onTextFocus} />
  }

  onTextFocus = () => {
    this.setState({ inputWidth: 'auto' })
    if (this.props.onFocus) {
      this.props.onFocus()
    }
  }
}

export default SafeTextInput
hramos commented 5 years ago

What's the latest minimal reproduction for this? From a quick read, it looks like TextInput does support copy/paste. There's two problems reported so far in this thread: Handoff may be affecting paste functionality, and using TextInput inside a KeyboardAvoidingView might also interfere.

If we can change this issue from a "feature request" (i.e. add support for copy/paste functionality) to a "bug report" (i.e. the functionality already exists, but it fails in X or Y scenario), it might improve its chances of getting fixed.

nosphera commented 5 years ago

I solved this by adding the property "removeClippedSubviews={false}" to my ScrollView and encapsulating the content inside a KeyboardAvoidingView.

 <ScrollView
    contentContainerStyle={Styles.contentContainerStyle}
    keyboardShouldPersistTaps="handled"
    removeClippedSubviews={false}
 >

     <KeyboardAvoidingView>

          <Text style={Styles.labelPageTitle}>
            {'bla bla bla'}
          </Text>
          <Text>
              {'bla bla bla'}
          </Text>
          <TextInput
            onChangeText={text => this.setState({ title: text })}
            style={Styles.textInput}
            value={title}
          />

    </KeyboardAvoidingView>

</ScrollView>
owinter86 commented 5 years ago

@hramos I have been able to reproduce this into a simple example, as per below. Copy paste will break on android if a parent view has removeClippedSubviews enabled, and the textInput is wrapped in a view with certain styles. See below.

function Input() {
  return (
    <View
      // This breaks copy/paste on android with certain styles
      removeClippedSubviews={true}
      style={{
        flex: 1,
        justifyContent: "center",
        padding: 40,
        backgroundColor: "gray"
      }}
    >
      <View style={{ elevation: 1 }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View style={{ borderWidth: 1 }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View style={{ backgroundColor: "white" }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View>
        <TextInput placeholder="Can Copy/Paste" style={{ backgroundColor: "white" }} />
      </View>

      <TextInput placeholder="Can Copy/Paste" style={{ backgroundColor: "white" }} />
    </View>
  );
}

I think more people will have this issue as I believe createBottomTabNavigator from react-navigation uses removeClippedSubviews for performance improvements. I can replicate the same issue by removing the removeClippedSubviews prop from the parent view, but then wrapping it in the createBottomTabNavigator, as per below.

function Input() {
  return (
    <View
      style={{
        flex: 1,
        justifyContent: "center",
        padding: 40,
        backgroundColor: "gray"
      }}
    >
      <View style={{ elevation: 1 }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View style={{ borderWidth: 1 }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View style={{ backgroundColor: "white" }}>
        <TextInput placeholder="broken copy/paste" style={{ backgroundColor: "white" }} />
      </View>

      <View>
        <TextInput placeholder="Can Copy/Paste" style={{ backgroundColor: "white" }} />
      </View>

      <TextInput placeholder="Can Copy/Paste" style={{ backgroundColor: "white" }} />
    </View>
  );
}

const Navigator = createBottomTabNavigator({
  Input
});

const Navigation = createAppContainer(Navigator);

export default Navigation;
vgm8 commented 5 years ago

Something weird is happening to me. When I click for the first time in an empty input, it doesn't show the paste option. Once I write something in the input and delete it the paste option shows correctly. I don't know why I can not paste in an empty input. I've tried not using scroll, use removeClippedSubviews prop... but nothing is working. Any help? Thanks.

AzizStark commented 5 years ago

I was using createMaterialBottomNavigator from react-navigator which caused this issue for me.

I spent to whole night trying to fix this. The problem is the removeClippedSubviews is set as true. After setting it to false the clipboard appeared. react-navigation uses react-native-paper which is also has this setting that prevents the clipboard options from appearing.

I solved this by setting it to false in five files.

Path to files:

  1. \..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js

  2. \..\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js

  3. \..\node_modules\react-navigation-drawer\dist\view\ResourceSavingScene.js

  4. \..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\dist\views\ResourceSavingScene.js

  5. \..\node_modules\react-native-paper\src\components\BottomNavigation.js

--

removeClippedSubviews={
                  // On iOS, set removeClippedSubviews to true only when not focused
                  // This is an workaround for a bug where the clipped view never re-appears
                  Platform.OS === 'ios' ? navigationState.index !== index : true  //<--  set this to false
                }
helenzhou6 commented 5 years ago

I've encountered a similar problem, where adding removeClippedSubviews seems to break copying/pasting into an input on Android:

Link to Snack here

Code:

import * as React from 'react';
import { Text, View, StyleSheet, TextInput, KeyboardAvoidingView } from 'react-native';
import Constants from 'expo-constants';

function Input() {
  return (
    <KeyboardAvoidingView>
      <Text selectable>To replicate - copy this text</Text>
      <View
        // removeClippedSubviews breaks copy/paste on android
        removeClippedSubviews={true}
        style={{
          backgroundColor: "lightgray"
        }}
      >
        <Text>Broken Inputs </Text>

        <View style={{ elevation: 1 }} removeClippedSubviews={false}>
          <TextInput
            placeholder="broken copy/paste"
            style={{ backgroundColor: "white" }}
          />
        </View>

        <View style={{ borderWidth: 1 }}>
          <TextInput
            placeholder="broken copy/paste"
            style={{ backgroundColor: "white" }}
          />
        </View>

        <View style={{ backgroundColor: "white" }}>
          <TextInput
            placeholder="broken copy/paste"
            style={{ backgroundColor: "white" }}
          />
        </View>

        <TextInput
          placeholder="broken copy/paste"
          style={{ backgroundColor: "lightblue" }}
        />
      </View>

      <View>
        <Text>Working Inputs </Text>

        <View>
          <TextInput
            placeholder="Can Copy/Paste"
            style={{ backgroundColor: "white" }}
          />
        </View>

        <View style={{ backgroundColor: "lightblue" }}>
          <TextInput placeholder="Can Copy/Paste" />
        </View>

        <TextInput
          placeholder="Can Copy/Paste"
          style={{ backgroundColor: "white" }}
        />
      </View>
    </KeyboardAvoidingView>
  );
}

export default class App extends React.Component {
  render() {
    return (
      <View style={styles.container}>
        <Input />
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    paddingTop: Constants.statusBarHeight,
    backgroundColor: 'lightgrey',
    padding: 8,
  },
});
blastering66 commented 5 years ago

This Simple Hack Saves me.

https://github.com/facebook/react-native/issues/9958#issuecomment-365624780

Basicly just put width dynamicly, do some delay and update state for its width to 100%

vorasudh commented 5 years ago

I have a different scenario. Paste option works just fine for iOS, but has weird issue on Android:

My TextInput is enclosed in a View. If I try to directly paste something there, it does not show paste option. But if I type atleast one letter and remove it and then try to paste something, it works.

annieneedscoffee commented 5 years ago

I was using createMaterialBottomNavigator from react-navigator which caused this issue for me.

I spent to whole night trying to fix this. The problem is the removeClippedSubviews is set as true. After setting it to false the clipboard appeared. react-navigation uses react-native-paper which is also has this setting that prevents the clipboard options from appearing.

I solved this by setting it to false in five files.

Path to files:

  1. ..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js
  2. ..\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js
  3. ..\node_modules\react-navigation-drawer\dist\view\ResourceSavingScene.js
  4. ..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\dist\views\ResourceSavingScene.js
  5. ..\node_modules\react-native-paper\src\components\BottomNavigation.js

--

removeClippedSubviews={
                 // On iOS, set removeClippedSubviews to true only when not focused
                 // This is an workaround for a bug where the clipped view never re-appears
                 Platform.OS === 'ios' ? navigationState.index !== index : true  //<--  set this to false
               }

Thank you @AzizStark your answer is what worked for me. Nothing else I tried worked because react-navigation kept on overriding everything else I tried. I'm using Platform.OS === 'ios' ? !isVisible : undefined instead of Platform.OS === 'ios' ? !isVisible : false because both true and false override certain behaviors I want on certain screens. Writing in undefined just prevents there error I get if I leave that space blank.

AzizStark commented 5 years ago

I was using createMaterialBottomNavigator from react-navigator which caused this issue for me. I spent to whole night trying to fix this. The problem is the removeClippedSubviews is set as true. After setting it to false the clipboard appeared. react-navigation uses react-native-paper which is also has this setting that prevents the clipboard options from appearing. I solved this by setting it to false in five files. Path to files:

  1. ..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js
  2. ..\node_modules\react-navigation-tabs\src\views\ResourceSavingScene.js
  3. ..\node_modules\react-navigation-drawer\dist\view\ResourceSavingScene.js
  4. ..\node_modules\react-navigation-material-bottom-tabs\node_modules\react-navigation-tabs\dist\views\ResourceSavingScene.js
  5. ..\node_modules\react-native-paper\src\components\BottomNavigation.js

--

removeClippedSubviews={
                 // On iOS, set removeClippedSubviews to true only when not focused
                 // This is an workaround for a bug where the clipped view never re-appears
                 Platform.OS === 'ios' ? navigationState.index !== index : true  //<--  set this to false
               }

Thank you @AzizStark your answer is what worked for me. Nothing else I tried worked because react-navigation kept on overriding everything else I tried. I'm using Platform.OS === 'ios' ? !isVisible : undefined instead of Platform.OS === 'ios' ? !isVisible : false because both true and false override certain behaviors I want on certain screens. Writing in undefined just prevents there error I get if I leave that space blank.

I am glad, It helped!

stale[bot] commented 4 years ago

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

stale[bot] commented 4 years ago

Closing this issue after a prolonged period of inactivity. If this issue is still present in the latest release, please feel free to create a new issue with up-to-date information.