React Tutorial — City/State Lookup using the US Postal Service API
Tags: react, javascript, tutorial, netlify
Published: 2020-12-17 14:58:29

## Introduction

User experience applies to every part of a website, including forms. You have to pay attention to accessibility, ease of use, and convenience. A form with good UX is easy to understand and easy to use. Who likes filling in forms? Umm, nobody! Using this thought process, I began to research what can I do to make an applicant form at the [Vets Who Code website](www.vetswhocode.io/apply) easier to use. I thought a good idea would to make the city and state self populate based on a user's U.S. Postal Code (Applicants are all veterans of US Forces). I started studying solutions. One was to use [ZipCodeAPI](https://www.zipcodeapi.com/) but they charge for more than 10 requests per hour, and I am not in a position to pay for their service. Here at Vets Who Code, we like to build our own tools. I immediately thought, "How hard can it be to make my own zip code API for our use?" It appears it's not hard to get the basic functionality using the [United States Postal Service's Web Tools](https://www.usps.com/business/web-tools-apis/), a 100% free, U.S. tax-payer-funded service.

Here is what we are going to be building: https://citystatelookup.netlify.app/

## Goal

:black_square_button: Build a tool using React to `fetch` the city and state of user based on zipcode.
:black_square_button: Determine if entered zipcode is 5-digits.
:black_square_button: Determine if zipcode is valid.
:black_square_button: If the zipcode is valid, display city and state in the city/state input boxes.
:black_square_button: Add animation as the API "loads" the city and state.

### Front-end

:black_square_button: [React](https://reactjs.org/) for building the user interface
:black_square_button: Fetch API to GET items from the serverless function

### Backend

:black_square_button: Use [Netlify Dev](https://www.netlify.com/products/dev/) to create a serverless function
:black_square_button: Process zip code to xml data and request to API
:black_square_button: GET data from API

## Prerequisites

:white_check_mark: A basic understanding of HTML, CSS, and JavaScript.
:white_check_mark: A basic understanding of the DOM.
:white_check_mark: Yarn or npm & Nodejs installed globally.
:white_check_mark: For the above three steps this overview of React by Tania Rascia is a great start. => https://www.taniarascia.com/getting-started-with-react/
:white_check_mark: netlify-cli installed globally. `npm i -g netlify-cli` or `yarn add netlify-cli`
:white_check_mark: Sign up for [USPS Web Tools](https://www.usps.com/business/web-tools-apis/).
:white_check_mark: A code editor (I'm using [VS Code](https://code.visualstudio.com/)) I will do my best to show everything else.
:white_check_mark: [Netlify](https://www.netlify.com/) account.
:white_check_mark: [Github](https://github.com/) account.

## Typing vs Copying and Pasting Code

I am a very big believer in typing code out that you intend to use for anything. Typing code versus copypasta provides a better learning return on investment because we're practicing instead of just reading. When we copy code without understanding it, we have a lesser chance of understanding what is happening. While it's nice to see our outcomes immediately the reward comes from understanding what we are doing. With that said, please don't copy and paste the code from this tutorial. Type. Everything. Out. You'll be a better programmer for it, trust me.

## CORS :smiling_imp:

Loading publicly accessible APIs from the frontend during development presents some problems. Mainly **Cross-Origin Resource Sharing** (CORS). CORS is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts.

> Ever seen this error?

![CORS Policy](https://citystatezipcode.s3.amazonaws.com/corspolicy.png)

## Setup

Going under the assumption that you have a basic understanding of HTML, CSS, and JavaScript, I am assuming you have installed `npm` or `yarn`, the latest version of `node`, React, `netlify-cli`, have a GitHub and Netlify account, and have registered to use USPS WebTools.

1. Create a new repo on github.
2. Create a new React site by typing `npx create-react-app <new-github-repo-name>`
3. Navigate to your new folder by typing `cd <new-github-repo-name>`
4. Delete all the boilerplate React code in `App.js`, so you're left with this:

```jsx
import React from "react";
import "./App.css";

function App() {
  return <div className="App"></div>;
}

export default App;
```

5. This is one part you are allowed to copy and paste data. Delete all the CSS code in `App.css`.
6. Copy and paste the CSS code from this link => [App.css](https://citystatezipcode.s3.amazonaws.com/App.css).
7. Push the code to Github to the repo you created earlier using these instructions => https://docs.github.com/en/github/importing-your-projects-to-github/adding-an-existing-project-to-github-using-the-command-line
8. Go to app.netlify.com and login. Follow the instructions here to add your new site from Git => https://www.netlify.com/blog/2016/09/29/a-step-by-step-guide-deploying-on-netlify/

You should now be setup to start the tutorial

## Frontend Form

First, let's start our development server. Type `yarn start` or `npm start` into your terminal.

Since we are trying to fetch a city and state we need to create a form.

In the code below, we set a couple states using the React `useState()` hooks. We also set an initial value for the `cityState` so it starts as an empty string.

We also added `<code>` so we can view our inputs as they are updated. (This can be removed later)

City and state input boxes are `disabled` because we do not want our user to have the ability to change it. You can also use the `readonly` attribute as well. The difference is minor but may make a difference depending on the end state of your form and accessibility needs. A `readonly` element is just not editable, but gets sent when the form submits. A `disabled` element isn't editable and isn't sent on submit. Another difference is that `readonly` elements can be focused (and getting focused when "tabbing" through a form) while disabled elements cannot.

If you notice, there is nothing to `submit` the form because we are going to update the city and state as the user types into the zipcode input. You will also notice that you can't actually type anything into the form. We will fix this next.

<p class="codeblock-label">App.js</p>

```jsx
import React, { useState } from "react";
import "./App.css";

function App() {
  const initialCityState = { city: "", state: "" };
  const [cityState, setCityState] = useState(initialCityState);
  const [zipcode, setZipcode] = useState("");
  return (
    <div className="App">
      <h1>City/State Lookup Tool</h1>
      <form action="" className="form-data">
        <label htmlFor="zip">Type Zip Code Here</label>
        <input
          className="zip"
          value={zipcode}
          placeholder="XXXXX"
          type="text"
          name="zip"
          id="zip"
        />
        <label htmlFor="city">City</label>
        <input
          className={`city`}
          value={cityState.city}
          type="text"
          name="city"
          disabled
          id="city"
        />
        <label htmlFor="state">State</label>
        <input
          className={`state`}
          value={cityState.state}
          type="text"
          name="state"
          disabled
          id="state"
        />
      </form>
      <pre>
        <code>
          {JSON.stringify({
            zipcode: zipcode,
            city: cityState.city,
            state: cityState.state,
          })}
        </code>
      </pre>
    </div>
  );
}

export default App;
```

If you typed everything correctly, you should see this:

![React started](https://citystatezipcode.s3.amazonaws.com/yarnstart.PNG)

Let's add a little action to this form.

We add an `onChange` handler to our `zipcode` element so that we can update the zipcode.

We destructured the `value` from `event.target.value` to make it easier to read.

We also add some validation and an [input mask](https://dev.to/vetswhocode/how-to-normalize-an-input-colloquially-known-as-how-to-create-an-input-mask-5gh4); this way we can insure that a user will only enter numbers and that it will only be five numbers (The length of US Postal Codes). The `value.replace(/[^\d{5}]$/, "").substr(0, 5))` block has a regular expression to only allow numbers and the `substr` will only allow five in the form.

As you type in the form the code block at the bottom will update the zipcode.

<p class="codeblock-label">App.js</p>

```jsx
<input
  className="zip"
  value={zipcode || ""}
  placeholder="XXXXX"
  type="text"
  name="zip"
  id="zip"
  onChange={(event) => {
    const { value } = event.target;
    setZipcode(value.replace(/[^\d{5}]$/, "").substr(0, 5));
  }}
/>
```

This is what you should be left with:

![zip entered gif](https://citystatezipcode.s3.amazonaws.com/typezip.gif)

## Netlify Functions

The previously installed `netlify-cli` package comes with some cool tools. One of them creates a serverless function that acts as a go between the frontend and an API that the app is trying to connect with. To interface with Netlify follow these steps:

1. `netlify init` - This command is going to set off a chain of events. Firstly, it is going to ask for permission to access Netlify on your behalf. I would recommend clicking "Authorize". Close the browser and then return to your editor.
2. Next, Netlify is going to ask if you want to create a Netlify site without a git repo. Click "No, I will connect this directory with Github first. Follow the instructions. It's going to walk you through the process of setting up a new repo and pushing it up to your repo.
3. Type `netlify init` again.
4. Select `Create & configure a new site`. Part of the prerequisites required creating a Netlify account. This part will log you in to Netlify. After that, select your 'team'.
5. Name your site. It has a naming convention of only alphanumeric characters only; something like `city-state-lookup-tool` would work.
6. You'll now have your partially completed app online.
7. Next, select `Authorize with Github through app.netlify.com`. A new page will open asking you to allow Netlify access to your repo. Once you allow access, you can close that browser window.
8. The Netlify tool is going to ask you the build command for your site. For yarn it `CI=false yarn build`, for npm it's `CI=false npm run build`. The `CI=false` flag preceding the `build` command will stop treating warnings as errors, which will prevent your site from being built.
9. `Directory to deploy?` leave blank
10. `Netlify functions folder?` type `functions`
11. `No netlify.toml detected. Would you like to create one with these build settings?` Type `Y`
12. After this a series of steps will happen and you'll end up with `Success! Netlify CI/CD Configured!`.

A new file should have been created named `netlify.toml`. If you open it up it should look similar to this:

```toml
[build]
  command = "CI=false yarn build"
  functions = "functions"
  publish: "."
```

## Serverless Functions

To talk to our back end without any CORS issues we need to create a serverless function. A serverless function is an app that runs on a managed server, like AWS or in this case, Netlify. The companies then manage the the server maintenance and execution of the code. They are nice because the serverless frameworks handle the go between a hosted API and the frontend application.

![serverless architechture](https://citystatezipcode.s3.amazonaws.com/serverlessArchitechture.png)

1. In your terminal type `netlify functions:create`.
2. Typing this will create a dialog. Select `node-fetch`
3. Name your function something easy to remember like `getCityState`. If you observe, we now have a new folder located at the root of your directory named `functions`. In it should be the generated file named `getCityState.js` with a `node_modules` folder, and a few other files.
4. Open the `getCityState.js` file and delete the content below `const fetch = require("node-fetch")`

In the `getCityState.js` file add a couple of constants. One is for the secret key which we'll handle soon, one is for the API request link, and the last one is HTML headers which the frontend needs to handle permission to read what the function returns.

<p class="codeblock-label">getCityState.js</p>

```javascript
const fetch = require("node-fetch");

const USER_ID = process.env.REACT_APP_USERID;
const BASE_URI =
  "http://production.shippingapis.com/ShippingAPITest.dll?API=CityStateLookup&XML=";
const config = {
  headers: {
    "Content-Type": "text/xml",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Credentials": true,
    "Access-Control-Allow-Methods": "GET",
  },
  method: "get",
};
```

Below that add the main function:

<p class="codeblock-label">getCityState.js</p>

```javascript
exports.handler = async function (event, context) {
  // The zipcode is sent by the frontend application. 
  // This is where we use it.
  const zipcode = event.queryStringParameters.zipcode;

  // The xml variable is the string we are going to send to the
  // USPS to request the information
  const xml = `<CityStateLookupRequest USERID="${USERID}"><ZipCode ID="0"><Zip5>${zipcode}</Zip5></ZipCode></CityStateLookupRequest>`;
  try {
    // Using syntactic sugar (async/await) we send a fetch request
    // with all the required information to the USPS.
    const response = await fetch(`${BASE_URI}${xml}`, config);
    // We first check if we got a good response. response.ok is
    // saying "hey backend API, did we receive a good response?"
    if (!response.ok) {
      // If we did get a good response we store the response
      // object in the variable
      return { statusCode: response.status, body: response };
    }
    // Format the response as text because the USPS response is
    // not JSON but XML
    const data = await response.text();
    // Return the response to the frontend where it will be used.
    return {
      statusCode: 200,
      body: data,
    };
    // Error checking is very important because if we don't get a
    // response this is what we will use to troubleshoot problems
  } catch (err) {
    console.log("Error: ", err);
    return {
      statusCode: 500,
      body: JSON.stringify({ msg: err.message }),
    };
  }
};
```

**Add** a new file named `.env` the root of the project and add your user information from the USPS. When you signed up they should have sent an email with this information. The title of the email should be similar to **Important USPS Web Tools Registration Notice** from *registration@shippingapis.com*

<p class="codeblock-label">.env</p>

In the `.env` file:

```
# USPS API Info:
REACT_APP_USERID="1234567890123"
```

> **IMPORTANT!!!**
> ADD YOUR .ENV FILE TO THE `.gitignore` FILE

## Putting it all together

Up to this point, we've created a form where we can enter a zip code, sanitized our input, created a repo on Github, connected the repo to Netlify, and created a serverless function. Now it's time to put it all together and get some info from the USPS to display the city and state of the entered zip code by "fetching" the data.

In `App.js` import `useEffect` and add the `useEffect` hook

<p class="codeblock-label">App.js</p>

```jsx
import React, { useState, useEffect } from "react";

function App() {
  const initialCityState = { city: "", state: "" };
  const [cityState, setCityState] = useState(initialCityState);
  const [zipcode, setZipcode] = useState("");

  useEffect(() => {
    // Creating a new function named fetchCityState. 
    // We could have this outside the useEffect but this 
    // makes it more readable.
    const fetchCityState = async () => {
      // We are using a try/catch block inside an async function
      // which handles all the promises
      try {
        // Send a fetch request to the getCityState serverless function
        const response = await fetch(
          `/.netlify/functions/getCityState?zipcode=${zipcode}`,
          { headers: { accept: "application/json" } }
        );
        // We assign data to the response we receive from the fetch
        const data = await response.text();
        console.log(data)
        // Using a spread operator is an easy way to populate our city/state
        // form
        setCityState({...cityState, city: data, state: "" )
        // The catch(e) will console.error any errors we receive
      } catch (e) {
        console.log(e);
      }
    };
    // Run the above function
    fetchCityState();
    //The optional array below will run any time the zipcode
    // field is updated
  }, [zipcode]);
}
```

Let's go ahead and restart our development server, except this time use `netlify dev` instead of `yarn start` or `npm start`. We're using this command now because Netlify is going to start taking over things like the connection to our `getCityState` serverless function.

This is what you should see:

![xml object](https://citystatezipcode.s3.amazonaws.com/xmlobject.png)

If you type anything into the **Zip Code** field the `<code>` block below the form should update to show the city and state in the `<?xml>` field. Small problem though, we want to be able to use it. We'll take care of this next.

## Parsing XML to JSON

There are many tools out there to parse [xml to json](https://www.npmjs.com/search?q=xml%20parse) but I wanted a native solution. Sure, many of the tools out there cover edge cases but since we know what we are getting back from the USPS, I thought a more native solution to the problem would be better. As it stands this is what we are sending to the USPS:

<p class="codeblock-label">xml sent</p>

```html
<CityStateLookupRequest USERID="XXXXXXXXXXXX">
  <ZipCode ID="90210">
    <Zip5>20024</Zip5>
  </ZipCode>
</CityStateLookupRequest>
```

...and this is what we receive in the response:

<p class="codeblock-label">xml response</p>

```html
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<CityStateLookupResponse><ZipCode ID=\"0\"><Zip5>90210</Zip5><City>BEVERLY HILLS</City><State>CA</State></ZipCode></CityStateLookupResponse>"
```

Which is a stringified version of xml.

![](https://res.cloudinary.com/practicaldev/image/fetch/s--efGGmIaG--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://cdn-images-1.medium.com/max/1600/1%2AXuRqFNT2fMExddcrERq-6g.jpeg)

So how do we go about going from the stringified xml to something like this?

<p class="codeblock-label">json</p>

```json
[{ "ZipCode": 910210, "City": "BEVERLY HILLS", "State": "CA" }]
```

[DEV](https://dev.to) to the rescue!

I followed along with this article written by [Nitin Patel](https://dev.to/niinpatel)

{% user niinpatel %}

[Link to Nitin Patel article](https://dev.to/niinpatel/converting-xml-to-json-using-recursion-2k4j)

{% link niinpatel/converting-xml-to-json-using-recursion-2k4j %}

According to the article:

> Since, XML has a lot of nested tags, this problem is a perfect example of a practical application of recursion.

An elegant solution to a difficult problem. It uses the [**DOMParser Web API**](https://developer.mozilla.org/en-US/docs/Web/API/DOMParser) which according to the documentation it...

> parses XML or HTML to source code from a string into a DOM TREE.

Here's the function from the article:

<p class="codeblock-label">xml2json.js</p>

```javascript
function xml2json(srcDOM) {
  let children = [...srcDOM.children];

  // base case for recursion.
  if (!children.length) {
    return srcDOM.innerHTML;
  }

  // initializing object to be returned.
  let jsonResult = {};

  for (let child of children) {
    // checking is child has siblings of same name.
    let childIsArray =
      children.filter((eachChild) => eachChild.nodeName === child.nodeName)
        .length > 1;

    // if child is array, save the values as array, 
    // else as strings.
    if (childIsArray) {
      if (jsonResult[child.nodeName] === undefined) {
        jsonResult[child.nodeName] = [xml2json(child)];
      } else {
        jsonResult[child.nodeName].push(xml2json(child));
      }
    } else {
      jsonResult[child.nodeName] = xml2json(child);
    }
  }

  return jsonResult;
}
```

Let's type this into our `App.js` file right below the import statement.

We now have the last piece of our puzzle and should be able to parse the response from the USPS to something we can use.

Update the `fetchCityState` function inside the `useEffect` hook, and add the `DOMParser`

<p class="codeblock-label">App.js</p>

```jsx
const initialCityState = { city: "", state: "" };

// Add a new DomParser API object
const parser = new DOMParser();

const [cityState, setCityState] = useState(initialCityState);
const [zipcode, setZipcode] = useState("");

useEffect(() => {
  const fetchCityState = async () => {
    try {
      const response = await fetch(
        `/.netlify/functions/getCityState?&zipcode=${zipcode}`,
        {
          headers: { accept: "application/json" },
        }
      );
      const data = await response.text();

      // Use the DOMParser here. Remember it returns a DOM tree
      const srcDOM = parser.parseFromString(data, "application/xml");

      // Use the xml2json function
      const res = xml2json(srcDOM);

      // Let's see where we're at
      console.log(res);

      // Reset the city and state to empty strings.
      setCityState({ ...cityState, city: "", state: "" });
    } catch (e) {
      console.log(e);
    }
  };
  fetchCityState();
}, [zipcode]);
```

Here's what you should have in the console:

```json
{
  "CityStateLookupResponse": {
    "ZipCode": {
      "Zip5": "90210",
      "City": "BEVERLY HILLS",
      "State": "CA"
    }
  }
}
```

Now we have something to work with! An actual object full of json-juicy-goodness :copyright:. All we have to add is some conditionals and we'll be off to the races.

## Finishing up

Before we finish up let's figure out what we will need to check for:

1. Something to check for a a valid zip code before the `useEffect` is run. The pseudocode would be if zip is 5-characters long, then run the `useEffect`.
2. Some kind of loading conditional. `useState` is often used for this. We'll set the `useState` initially to false and in the `onChange` handler of the form we'll set the `useState` to true.
3. Finally we have to check for errors. If the response sends back that a zip code doesn't exist, we'll let the user know in the form.

Here it is:

<p class="codeblock-label">App.js</p>

```jsx
import React, { useEffect, useState } from "react";
import "./App.css";

const xml2json = (srcDOM) => {
  let children = [...srcDOM.children];
  // base case for recursion.
  if (!children.length) {
    return srcDOM.innerHTML;
  }
  // initializing object to be returned.
  let jsonResult = {};
  for (let child of children) {
    // checking is child has siblings of same name.
    let childIsArray =
      children.filter((eachChild) => eachChild.nodeName === child.nodeName)
        .length > 1;
    // if child is array, save the values as array, 
    // else as strings.
    if (childIsArray) {
      if (jsonResult[child.nodeName] === undefined) {
        jsonResult[child.nodeName] = [xml2json(child)];
      } else {
        jsonResult[child.nodeName].push(xml2json(child));
      }
    } else {
      jsonResult[child.nodeName] = xml2json(child);
    }
  }
  return jsonResult;
};

function App() {
  const parser = new DOMParser();

  const initialCityState = { city: "", state: "" };
  // eslint-disable-next-line
  const [cityState, setCityState] = useState(initialCityState);
  const [zipcode, setZipcode] = useState("");
  const [loading, setLoading] = useState(false);

  // We check to see if the input is 5 characters long and there
  // is something there
  const isZipValid = zipcode.length === 5 && zipcode;

  useEffect(() => {
    const fetchCityState = async () => {
      try {
        // If zip is valid then...fetch something
        if (isZipValid) {
          const response = await fetch(
            `/.netlify/functions/getCityState?&zipcode=${zipcode}`,
            {
              headers: { accept: "application/json" },
            }
          );
          const data = await response.text();
          const srcDOM = parser.parseFromString(data, "application/xml");
          console.log(xml2json(srcDOM));
          const res = xml2json(srcDOM);

          // Using optional chaining we check that all the DOM
          // items are there
          if (res?.CityStateLookupResponse?.ZipCode?.City) {
            // set loading to false because we have a result
            setLoading(false);
            // then spread the result to the setCityState hook
            setCityState({
              ...cityState,
              city: res.CityStateLookupResponse.ZipCode.City,
              state: res.CityStateLookupResponse.ZipCode.State,
            });

            // Error checking. User did not put in a valid zipcode
            // according to the API
          } else if (res?.CityStateLookupResponse?.ZipCode?.Error) {
            setLoading(false);
            // then spread the error to the setCityState hook
            setCityState({
              ...cityState,
              city: `Invalid Zip Code for ${zipcode}`,
              state: "Try Again",
            });
          }
        }
      } catch (e) {
        console.log(e);
      }
    };

    fetchCityState();
  }, [zipcode]);

  return (
    <div className="App">
      <h1>City/State Lookup Tool</h1>
      <form action="" className="form-data">
        <label htmlFor="zip">Type Zip Code Here</label>
        <input
          maxLength="5"
          className="zip"
          value={zipcode || ""}
          placeholder="XXXXX"
          type="text"
          name="zip"
          id="zip"
          onChange={(event) => {
            const { value } = event.target;
            // Set the loading to true so we show some sort of
            // progress
            setLoading(true);
            setCityState(initialCityState);
            setZipcode(value.replace(/[^\d{5}]$/, "").substr(0, 5));
          }}
        />
        <label htmlFor="city">City</label>
        <div className="input-container">
          <input
            className={`city`}
            value={cityState.city}
            type="text"
            name="city"
            disabled
            id="city"
          />
          <div className="icon-container">
            <i className={`${loading && isZipValid ? "loader" : ""}`}></i>
          </div>
        </div>
        <label htmlFor="state">State</label>
        <div className="input-container">
          <input
            className={`state`}
            value={cityState.state}
            type="text"
            name="state"
            disabled
            id="state"
          />
          <div className="icon-container">
            <i className={`${loading && isZipValid ? "loader" : ""}`}></i>
          </div>
        </div>
      </form>
      <pre>
        <code>
          {JSON.stringify({
            zipcode: zipcode,
            city: cityState.city,
            state: cityState.state,
          })}
        </code>
      </pre>
    </div>
  );
}

export default App;
```

And that's it! Run `netlify dev` and see your hard work payoff:

![final](https://citystatezipcode.s3.amazonaws.com/finaloutcome.png)

## Conclusion

Throughout this comprehensive tutorial we covered a lot! Firstly, we set up a form using the `useState` hook and also normalized our zip code input. Next was writing and tying serverless function to Netlify, and Github. Finally, we parsed to response from the USPS which was sent in `XML` to something easier to display. All of this contributed to increasing the UX.

## Vets Who Code

Did you like what you read? Want to see more? Let me know what you think about this tutorial in the comments below. As always, a donation to Vets Who Code goes to helping veteran, like myself, in learning front end development and other coding skills. You can donate here: [VetsWhoCode](https://vetswhocode.io) Thanks for your time!