Building a Unique check with React
April 11, 2017
Shopify has a fancy instant Front-End check when registering a new store to make sure the name is unique before you even submit the form. I wanted to build this as a React Component for a new project I’ve been working on.
What we will be building
The React Component
The first step is to define the basic of the component, we will be storing the company_name as they input it and whether or not the name is unique,
company_name_valid.
import React, { Component } from 'react';
export default class RegisterCompany extends Component {
constructor(props) {
super(props);
this.state = {
company_name: '',
company_name_valid: null, // null - have not checked, false - not unique, true - unique
}
this.inputChange = this.inputChange.bind(this);
}
inputChange(e){
this.setState({company_name: e.target.value, company_name_valid: null});
}
render(){
return(
<div>
<div className="card">
<div className="card-header">
Register a Business
</div>
<div className="card-block">
<div className="form-group">
<span className="input-icon">
<i className="fa fa-fw fa-id-badge"></i>
</span>
<input type="text" id="email" placeholder="Business Name" onChange={this.inputChange} value={this.state.company_name} className={`form-control ${this.state.company_name_valid ? 'form-control-success' : (this.state.company_name_valid === false ? 'form-control-danger' : '') }`} />
<span className="input-action">
<i className="fa fa-spinner fa-pulse"></i>
</span>
</div>
</div>
</div>
</div>
);
}
}
This class now simply displays the input field and stores the value of company_name when the user types. We now need to check if it unique via an API call to our application.
import axios from 'axios';
export default class RegisterCompany extends Component {
constructor(props) {
super(props);
this.state = {
...
fetching: false
}
...
this.inputCheck = this.inputCheck.bind(this);
}
...
inputKeyUp(e){
this.inputCheck(e.target.value);
}
inputCheck(){
this.setState({company_name_valid: null});
if(this.state.company_name !== ''){
axios.get('businesses/'+this.state.company_name+'/check')
.then(response => {
return response.data;
})
.then(data => {
this.setState({ company_name_valid: data.valid, fetching: false });
});
}
}
render(){
return(
<div>
...
<input type="text" id="email" placeholder="Business Name" onChange={this.inputChange} onKeyUp={this.inputKeyUp} value={this.state.company_name} className={`form-control ${this.state.company_name_valid ? 'form-control-success' : (this.state.company_name_valid === false ? 'form-control-danger' : '') }`} />
</div>
);
}
}
In the above example we are now checking on every single key up that the user enters, which means if they type New Business there will be 12 API calls to check each new string, first N then Nestops typing for an extended period of time (extended being maybe half of a second).
This is where a Debounce library comes in handy. After doing a
yarn add throttle-debounce --dev
import debounce from 'throttle-debounce/debounce'
export default class RegisterCompany extends Component {
constructor(props) {
...
this.inputCheck = debounce(500, this.inputCheck);
}
inputKeyUp(e){
this.inputCheck(e.target.value);
}
inputCheck(){
this.setState({company_name_valid: null});
if(this.state.company_name !== ''){
axios.get('businesses/'+this.state.company_name+'/check')
.then(response => {
return response.data;
})
.then(data => {
this.setState({ company_name_valid: data.valid, fetching: false });
});
}
}
render(){
return(
<div>
...
<input type="text" id="email" placeholder="Business Name" onChange={this.inputChange} onKeyUp={this.inputKeyUp.bind(this)} value={this.state.company_name} className={`form-control ${this.state.company_name_valid ? 'form-control-success' : (this.state.company_name_valid === false ? 'form-control-danger' : '') }`} />
</div>
);
}
}
Almost there!
The last thing we need to do now is to display some feedback to the user based on the API response. We will do this by adding a form_class to the form and some custom output to display the text response. We also want to show the submit button if the name is valid so the user can submit it.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import debounce from 'throttle-debounce/debounce'
import axios from 'axios';
import SubmitButton from './SubmitButton'
export default class RegisterCompany extends Component {
constructor(props) {
super(props);
this.state = {
company_name: '',
company_name_valid: null,
form_class: '',
fetching: false
}
this.inputChange = this.inputChange.bind(this);
this.inputCheck = debounce(650, this.inputCheck);
this.submitRegister = this.submitRegister.bind(this);
}
nameLabel(){
if (this.state.company_name_valid) {
return (
<div className="form-control-feedback">That Business name is available!</div>
)
}
else if (this.state.company_name_valid === false) {
return (
<div className="form-control-feedback">That Business name has already been taken</div>
)
}
}
ifValidNameFields(){
if (this.state.company_name_valid) {
return (
<div className="valid-name">
<SubmitButton text="Register Business" className="btn btn-primary btn-block" submitting={this.state.fetching} clicked={this.submitRegister}></SubmitButton>
</div>
)
}
}
inputChange(e){
this.setState({company_name: e.target.value, company_name_valid: null, form_class: ''});
}
inputKeyUp(e){
this.inputCheck(e.target.value);
}
inputCheck(){
this.setState({company_name_valid: null, form_class: 'has-loading'});
if(this.state.company_name !== ''){
axios.get('businesses/'+this.state.company_name+'/check')
.then(response => {
return response.data;
})
.then(data => {
this.setState({ company_name_valid: data.valid, fetching: false, form_class: data.valid ? 'has-success' : 'has-danger' });
});
}
}
submitRegister(){
this.setState({fetching: true});
axios.post('businesses', {
name: this.state.company_name
})
.then(function (response) {
window.location = '/business/account/'+response.data.slug;
})
.catch(function (error) {
console.log(error);
});
}
render(){
return(
<div>
<div className="card">
<div className="card-header">
Register a Business
</div>
<div className="card-block">
<div className={`form-group ${this.state.form_class}`}>
<span className="input-icon">
<i className="fa fa-fw fa-id-badge"></i>
</span>
<input type="text" id="email" placeholder="Business Name" onChange={this.inputChange} onKeyUp={this.inputKeyUp.bind(this)} value={this.state.company_name} className={`form-control ${this.state.company_name_valid ? 'form-control-success' : (this.state.company_name_valid === false ? 'form-control-danger' : '') }`} />
<span className="input-action">
<i className="fa fa-spinner fa-pulse"></i>
</span>
{ this.nameLabel() }
</div>
{ this.ifValidNameFields() }
</div>
</div>
</div>
);
}
}