DEV Community

Cover image for Street Address Form Validation with ReactJS and HERE Geocoder Autocomplete
Jayson DeLancey
Jayson DeLancey

Posted on • Originally published at developer.here.com

Street Address Form Validation with ReactJS and HERE Geocoder Autocomplete

If you want customers to buy from you, requiring a shipping or billing address may become part of your ReactJS application. Nearly every database course looks at postal addresses as a good baseline tutorial for how to organize your data model in normal form, but what about on the client-side?

To keep garbage out of your records you may want to take some time to address this with address validation. If you also want to improve the user experience, the HERE Geocoder Autocomplete API may help. This REST service lets you identify an address in fewer keystrokes which can help avoid spelling mistakes by providing suggestions with each character typed. There are also some powerful features like spatial and region filters to improve relevance as well.

Project

For this project, we want to create a simple address form as shown by this mockup.

Basic Mockup

As you start typing “2168 Sha” the rest of the address should be filled in automatically. We can then check the address with the HERE Geocoder API to be sure we can identify a latitude and longitude value.

Getting Started

Generating 29,542 files for what amounts to a single page form may seem like overkill, but I still recommend starting with create-react-app. The development tools like a local server with live-reloading in a browser are helpful for quick iterations. Let’s get started by running the following:

create-react-app app
cd app
npm install bootstrap axios
npm start
Enter fullscreen mode Exit fullscreen mode

As you can see I’ll also be using Bootstrap as the CSS framework and axios as an HTTP client.

Thinking in React

Following the concepts from the ReactJS tutorial Thinking in React we will follow a few basic steps.

  1. Break the UI into a component hierarchy
  2. Build a static version
  3. Identify the minimal representation of UI state
  4. Identify where the state should live
  5. Add inverse data flow

Break the UI into a component hierarchy

Using our mockup from before, it looks like we can break the page up into a number of components.

Component Labels

Build a static version

Starting from the bottom, the AddressItem is a single row item consisting of a label and form element. I’m using ES6 classes and exporting the class for use by other components.

import React, { Component } from 'react';

class AddressItem extends Component {
  render() {
    return (
        <div className="row form-group justify-content-start">
            <label className="col-sm-4 col-form-label">{this.props.label}</label>
            <div className="col-xl-8">
              <input
                type="text"
                defaultValue={this.props.value}
                onChange={this.props.onChange}
                className="form-control"
                placeholder={this.props.placeholder} />
            </div>
        </div>
      );
  }
}

export default AddressItem;

Enter fullscreen mode Exit fullscreen mode

The AddressSuggest and AddressInput components both make use of the AddressItem in their render methods.

// app/src/AddressSuggest.js
class AddressSuggest extends Component {
  render() {
    return (
      <AddressItem label="Address" value={this.props.query} placeholder="start typing" />
      );
  }
}
Enter fullscreen mode Exit fullscreen mode

You may recognize that I've skipped the import and exports here for brevity but they are still required. You can find those in the full listing from the GitHub repository linked at the end.

// app/src/AddressInput.js
class AddressInput extends Component {
  render() {
    return (
      <div className="card"><div className="card-body">
      <AddressItem label="Street" value={this.props.street} placeholder="" readonly="true" />
      <AddressItem label="City" value={this.props.city} placeholder="" readonly="true" />
      <AddressItem label="State" value={this.props.state} placeholder="" readonly="true" />
      <AddressItem label="Postal Code" value={this.props.code} placeholder="" readonly="true" />
      <AddressItem label="Country" value={this.props.country} placeholder="" readonly="true" />
      </div></div>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Continuing up the hierarchy, the AddressForm combines the whole input form along with submission buttons to do our validation.

// app/src/AddressForm.js
class AddressForm extends Component {
  render() {
    return (
        <div className="container">
          <AddressSuggest />
            query="4168 S"
            />
          <AddressInput
            street="4168 Shattuck Ave"
            city="Berkeley"
            state="CA"
            code="94704"
            country="USA"
            />
          <br/>
          <button type="submit" className="btn btn-primary">Check</button>
          <button type="submit" className="btn btn-outline-secondary">Clear</button>
        </div>
      );
  }
}

Enter fullscreen mode Exit fullscreen mode

As you can see we just hard-coded some static values in as properties to see how our form will look before we need to deal with any interactive behaviors. Up next, we need to replace some of those properties with state.

Tracking State

Up to this point we’ve only used immutable properties. Now we want to go back and start tracking state. The minimal information we want to track:

  • search query entered by the user which changes over time
  • postal address can be computed, but can also change over time by user input

The AddressForm is a common ancestor in the hierarchy for these two related components that we want to keep in sync. As the user starts typing text in the AddressSuggest we will query the HERE Geocoder Autocomplete API and update AddressInput.

Looking at the HERE Autocompletion JavaScript Demo we see the required parameters of query, app_id, and app_code. Unlike that demo, we will use axios as the HTTP client for making requests. The constant variables APP_ID_HERE and APP_CODE_HERE also need to be defined to be referenced as seen in the code below.

The AddressForm now looks like:


class AddressForm extends Component {
  constructor(props) {
    super(props);

    const address = this.getEmptyAddress();
    this.state = {
      'address': address,
      'query': '',
      'locationId': ''
    }

    this.onQuery = this.onQuery.bind(this);
  }

  onQuery(evt) {
    const query = evt.target.value;
    if (!query.length > 0) {
      const address = this.getEmptyAddress();
      return this.setState({
        'address': address,
        'query': '',
        'locationId': ''
        })
    }

    const self = this;
    axios.get('https://autocomplete.geocoder.api.here.com/6.2/suggest.json', {
      'params': {
        'app_id': APP_ID_HERE,
        'app_code': APP_CODE_HERE,
        'query': query,
        'maxresults': 1,
      }}).then(function (response) {
        const address = response.data.suggestions[0].address;
        const id = response.data.suggestions[0].locationId;
        self.setState({
          'address': address,
          'query': query,
          'locationId': id,
          });
      });
  }

  render() {
    return (
      <div class="container">
        <AddressSuggest
          query={this.state.query}
          onChange={this.onQuery}
          />
        <AddressInput
          street={this.state.address.street}
          city={this.state.address.city}
          state={this.state.address.state}
          postalCode={this.state.address.postalCode}
          country={this.state.address.country}
          />
        ...
      );
  }
}

Enter fullscreen mode Exit fullscreen mode

The response from the Geocoder Autocomplete includes an array of suggestions. Two valuable pieces of information there include the locationId if we wanted to do a full geocoder lookup by id to get the latitude and longitude. Included is also an address block which details the city, country, street, state, and postalCode for display in our form.

Inverse Data Flow

You may have noticed that for our AddressSuggest component we added a onChange={this.onQuery}. This pushes this method down to lower-level components. Those lower level components need to respond to user input which should be easy now that we’ve passed a reference to this handler as a property as seen in the AddressSuggest component.


return (
    <AddressItem
      label="Address"
      value={this.props.query}
      onChange={this.props.onChange}
      placeholder="start typing" />
);
Enter fullscreen mode Exit fullscreen mode

It is worth noting that each character typed by the user triggers this event. Since each event fires off a request to the Geocoder Autocomplete service we can quickly rack up many transactions. A final solution may make more efficient use of how these events are handled or display a lookahead of more than one suggestion at a time by changing maxresults=10.

Validation

Up to this point we’ve helped the user by using their input as suggestions for guessing the correct address with less typing and errors. Once the address is input though, now we want to check it. We need to implement the behavior of our check and clear buttons using the HERE Geocoder.

First, let’s modify our rendered elements to include a result and onClick event callbacks.

{ result }
<button type="submit" className="btn btn-primary" onClick={this.onCheck}>Check</button>
<button type="submit" className="btn btn-outline-secondary" onClick={this.onClear}>Clear</button>
Enter fullscreen mode Exit fullscreen mode

We also make sure that all of our event handlers are bound in the constructor. This makes sure that this is an available reference. We then have methods defined for each of these cases.


// User has entered something in address suggest field
this.onQuery = this.onQuery.bind(this);
// User has entered something in address field
this.onAddressChange = this.onAddressChange.bind(this);
// User has clicked the check button
this.onCheck = this.onCheck.bind(this);
// User has clicked the clear button
this.onClear = this.onClear.bind(this);
Enter fullscreen mode Exit fullscreen mode

The clear handler is pretty straightforward, just calling setState() for returning everything to the initial state as it was when constructor originally runs. The check handler is much more involved. Let’s look at it in a few pieces. This first section is initializing the parameters for the Geocoder service. If we used the Geocoder Autocomplete to find a suitable address, we should already have a LocationId that we can use. If we don’t have that or the user entered text into the various fields – then we’ll construct a search string with whatever details are provided.


onCheck(evt) {
  let params = {
    'app_id': APP_ID_HERE,
    'app_code': APP_CODE_HERE
  }

  if (this.state.locationId.length > 0) {
    params['locationId'] = this.state.locationId;
  } else {
    params['searchtext'] = this.state.address.street
      + this.state.address.city
      + this.state.address.state
      + this.state.address.postalCode
      + this.state.address.country
  }
  ...
}

Enter fullscreen mode Exit fullscreen mode

With the parameters in place, we again use axios to fetch a response from the geocoder REST API. If we get back a promised response with a matching location we can set the state to the appropriate success or error conditions.


onCheck(evt) {
  ...

  const self = this;
  axios.get('https://geocoder.api.here.com/6.2/geocode.json',
    { 'params': params }
  ).then(function(response) {
    const view = response.data.Response.View
    if (view.length > 0 && view[0].Result.length > 0) {
      const location = view[0].Result[0].Location;

      self.setState({
        'isChecked': 'true',
        'coords': {
            'lat': location.DisplayPosition.Latitude,
            'lon': location.DisplayPosition.Longitude
        },
        'address': {
          'street': location.Address.HouseNumber + ' ' + location.Address.Street,
          'city': location.Address.City,
          'state': location.Address.State,
          'postalCode': location.Address.PostalCode,
          'country': location.Address.Country
        }});
    } else {
      self.setState(
        'isChecked': true,
        'coords': null
        );
    }

  })
  ...
}

Enter fullscreen mode Exit fullscreen mode

Getting a latitude and longitude alone is not the best indicator so it is also important to look at the MatchLevel, MatchType, and MatchQuality to evaluate whether the address is valid.


"Result": [
          {
            "Relevance": 1,
            "MatchLevel": "houseNumber",
            "MatchQuality": {
              "State": 1,
              "City": 1,
              "Street": [
                1
              ],
              "HouseNumber": 1
            },
            "MatchType": "pointAddress",
            ...

Enter fullscreen mode Exit fullscreen mode

You can learn more about the address details like this from the Geocoder Search Response documentation.

For the full source listing, please review the GitHub repository HERE-react-address-validation.

animated gif

Summary

This demonstration looked into how to think about creating components with React so that we could build a smart address form with the HERE Geocoder Autocomplete API . We also did some additional validation using the HERE Geocoder API to retrieve a latitude and longitude to help us test our match quality. Street address validation for deliverability can be a complex subject but hope this gets you started.

There are many other ways to do client-side address validation which haven’t been covered here but hope that seeing how to use a Geocoder as a component can be valuable for better user experience. If you use other libraries like Formik, react-final-form or informed and have other ideas about how to do address validation with React let me know in the comments.

Top comments (1)

Collapse
 
evanpalsson profile image
Evan Palsson

Jayson, are you able to update this article? HERE has changed their API to Geocoding & Search API v7. I'm a novice/hobbyist developer, so these code walkthroughs are very helpful!