6

I'd like to get a TextField to select the whole text currently in the field whenever I click/tap/focus on the field. The following code works in Chrome (71.0.3578.98), but not in Safari (12.0.2). Any ideas why?

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <>
      <h1>Test Focus React</h1>
      <input
        type="text"
        defaultValue="test"
        onFocus={event => {
          event.target.select();
        }}
      />
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit pkqzz7nxz0

And yet this static HTML file without any React works fine on Safari.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Test Focus JS</title>
  </head>
  <body>
    <h1>Test Focus JS</h1>
    <input type="text" value="test" onClick="this.select();" />
  </body>
</html>

Edit 6l20oq76jn

Can anyone help me see how to get the selection to work on Safari with React?

Praveen Rao Chavan.G
  • 2,772
  • 3
  • 22
  • 33
EFC
  • 1,890
  • 18
  • 39

3 Answers3

4

When things like interacting with the DOM fail, it usually is related to how events fire synchronously / asynchronously in different contexts (react vs onclick), and browsers even (with Safari having weird optimisations sometimes).

I'm guessing it'll just work by making it async, like:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  return (
    <>
      <h1>Test Focus React</h1>
      <input
        type="text"
        defaultValue="test"
        onFocus={event => {
              // event properties must be copied to use async
              const target = event.target;
              setTimeout(() => target.select(), 0);
        }}
      />
    </>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit test-focus-react

Meligy
  • 35,654
  • 11
  • 85
  • 109
  • That did the trick on desktop Safari, but seems to still fail on Mobile Safari. The `ref` method also fails on Mobile Safari. However, if I replace select with [`setSelectionRange(0, 9999)`](https://stackoverflow.com/a/6302507/383737), then it works on Mobile Safari, Safari, and Chrome. – EFC Jan 18 '19 at 06:45
  • Hm, this solution worked for me; I tested it on iPhone on iOS 16 safari — thanks Meligy! – revelt Sep 23 '22 at 00:20
1

I think you want to use a React ref to store a reference to the actual input DOM element so that you can call select on it from the onClick method.

See the docs, they have a good example that you can modify a little bit to meet your needs: https://reactjs.org/docs/refs-and-the-dom.html

This should work, I think:

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);

    this.textInput = React.createRef();
    this.selectTextInput = this.selectTextInput.bind(this);
  }

  selectTextInput() {
    this.textInput.current.select();
  }

  render() {
    return (
      <div>
        <input 
          type="text" 
          defaultValue="pizza" 
          ref={this.textInput} 
          onClick={this.selectTextInput}
        />
      </div>
    );
  }
}

function App() {
  return (
    <CustomTextInput />
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Aaron
  • 7,055
  • 2
  • 38
  • 53
  • Thanks, this seems to work (though may need `setSelectionRange(0,9999)` instead of `select()` for Mobile Safari). However, this is just a bit too complex for my actual use case. Working with refs gets squirrelly when the forms get more complicated and Typescript enters the picture. – EFC Jan 18 '19 at 06:53
  • I would imagine going with an async `setTimeout` approach could also get squirrelly as the complexity of your form grows. ;-) – Aaron Jan 18 '19 at 14:42
  • What I find easier in the other approach is that a number of elements can share the same handler with no need to define refs. I can get to the proper targets with `event.currentTarget` and not have to worry about "who" contacted the handler at all. This feels simpler. Of course, I may encounter a downside along the way and appreciate your lesson on using `ref`. – EFC Jan 18 '19 at 15:31
1

This is well known issue, you can work around this basically using set timeout

onFocus={event => {
  setTimeout(event.target.select.bind(event.target), 20);
}}

Here is working example. I tested on safari worked without any issue.

Whatatimetobealive
  • 1,353
  • 10
  • 15