Reputation: 110960
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:
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
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:
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:
<Input />
- to render a controlled input<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