AnApprentice
AnApprentice

Reputation: 110960

With React, how to bind to the user's clipboard paste event and parse data?

I need to build a email verification for which requires a code sent to the user's email address. Very similar to the way Slack handle's email verification in their signup flow:

Slack

When you paste, the pasted text is inputed in one input after the other. With react, what is the right way to go about implementing a feature like this?

After componentDidMount, should I be binding and capturing the paste keyPress? Is that the right approach?

Upvotes: 7

Views: 9525

Answers (1)

Brett DeWoody
Brett DeWoody

Reputation: 62771

Super simple example to get you started in the right direction. This would need some work before being used. Here's what it does:

  • Allows the user to paste in a code and fills each input with the value
  • Allows the user to type in a code
  • Allows the user to edit a code
  • When the user enters a value, it shifts the focus to the next input
  • Only allows numeric inputs
  • Only allows a single number in each input

There's nothing particularly tricky here. We're using local state for the sake of the example, but this could be moved to another state management implementation like Redux.

The demo uses two components:

  1. <Input /> - to render a controlled input
  2. <App /> to render a container for the <Input />

The <App /> component handles the onPaste event, and passes the appropriate value from the pasted data to each <Input /> component

Each <Input /> component contains a controlled <input/> element, which consists of only a value.

// A functional component to keep it simple
class Input extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ''
    };
  }
  
  handleKeyDown = (event) => {
    // Handle the delete/backspace key
    if (event.keyCode === 8 || event.keyCode === 46) {
      this.setState({
        value: ''
      });
      
      return;
    }
    
    // Handle the tab key
    if (event.keyCode === 9) {
      return;
    }
    
    // Handle numbers and characters
    const key = String.fromCharCode(event.which);
    if (Number.isInteger(Number(key))) {
      this.setState({
        value: key
      }, () => {
        // Move focus to next input
        this.refs[(this.props.index + 1) % 6].focus()
      });
    }
  }
  
  componentWillReceiveProps = (nextProps) => {
    if (nextProps.value !== this.state.value) {
      this.setState({
        value: nextProps.value
      })
    }
  }

  render() {
    return (
      <div className="inputContainer">
        <input 
          className="input"
          value={this.state.value} 
          onKeyDown={this.handleKeyDown}
          ref={(ref) => this.refs[this.props.index] = ref}
          maxLength="1"
        />
      </div>
    )
  }
}

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      code: null
    }
  }

  handlePaste = (event) => {
    event.clipboardData.items[0].getAsString(text => {
      const code = text.split("").map((char, index) => {
        if (Number.isInteger(Number(char))) {
          return Number(char);
        }
        
        return "";
      });
      
      this.setState({
        code
      });
    })
  }
  
  render() {
    const code = this.state.code;
  
    return (
      <div className="container" onPaste={this.handlePaste}>
        <Input value={code && code[0]} index={0} />
        <Input value={code && code[1]} index={1} />
        <Input value={code && code[2]} index={2} />
        <div className="spacer">-</div>
        <Input value={code && code[3]} index={3} />
        <Input value={code && code[4]} index={4} />
        <Input value={code && code[5]} index={5} />
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
.container {
  display: flex;
}

.inputContainer {
  flex: 1;
  border: 1px solid #cccccc;
}

.inputContainer:last-child {
  border-top-right-radius: 5px;
  border-bottom-right-radius: 5px;
}

.inputContainer:first-child {
  border-top-left-radius: 5px;
  border-bottom-left-radius: 5px;
}

.spacer {
  flex: 0.3 0;
  text-align: center;
  height: 40px;
  line-height: 40px;
  font-size: 24px;
}

.input {
  width: 100%;
  height: 40px;
  line-height: 40px;
  font-size: 24px;
  text-align: center;
  border: none;
  outline: none;
  border-radius: 5px;
  box-sizing: border-box;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Test Code: 135791

<div id="app"></div>

Upvotes: 8

Related Questions