ReactNext.jsTypeScript

How to build a ZIP Code Radius Search with React

This guide demonstrates how to build a client-side React component that interacts with a Next.js API route to perform a radius search using the Payloadic ZIP Code API. This is a common pattern for building interactive location-based features.

The Architecture

We'll create two main parts: a Next.js API route that securely communicates with the ZIP Code API, and a React component that provides the user interface for the search. This separation is crucial for security, as it prevents your API key from being exposed on the frontend.

'use client';

import { useState, FormEvent } from 'react';

// Define the types for our data
interface ApiResult {
  zipcode: string;
  city: string;
  state: string;
  distance: number;
}

interface ApiResponse {
  results: ApiResult[];
}

export function ZipCodeRadiusSearch() {
  const [zipcode, setZipcode] = useState("90210");
  const [radius, setRadius] = useState("10");
  const [results, setResults] = useState<ApiResult[]>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setError(null);

    try {
      const response = await fetch('/api/zip-radius?zipcode=' + zipcode + '&radius=' + radius);
      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || 'An error occurred.');
      }
      const data: ApiResponse = await response.json();
      setResults(data.results || []);
    } catch (err: any) {
      setError(err.message);
    }
  };

  return (
    <div className="p-4 border rounded-lg">
      <form onSubmit={handleSubmit} className="flex items-end gap-4 mb-4">
        <div>
          <label htmlFor="zipcode" className="block text-sm font-medium">ZIP Code</label>
          <input
            id="zipcode"
            type="text"
            value={zipcode}
            onChange={(e) => setZipcode(e.target.value)}
            className="w-full px-3 py-2 border rounded-md"
            placeholder="e.g., 90210"
            required
          />
        </div>
        <div>
          <label htmlFor="radius" className="block text-sm font-medium">Radius (miles)</label>
          <select
            id="radius"
            value={radius}
            onChange={(e) => setRadius(e.target.value)}
            className="w-full px-3 py-2 border rounded-md"
          >
            <option value="5">5</option>
            <option value="10">10</option>
            <option value="25">25</option>
            <option value="50">50</option>
          </select>
        </div>
        <button
          type="submit"
          disabled={loading}
          className="px-4 py-2 bg-accent text-accent-foreground rounded-md disabled:opacity-50"
        >
          {loading ? 'Searching...' : 'Search'}
        </button>
      </form>

      {error && <p className="text-red-500">{'Error: ' + error}</p>}

      {results.length > 0 && (
        <div className="mt-4">
          <h3 className="text-lg font-semibold">Results</h3>
          <ul className="list-disc list-inside mt-2 space-y-1">
            {results.map((item) => (
              <li key={item.zipcode}>
                {item.zipcode + ' - ' + item.city + ', ' + item.state + ' (' + item.distance.toFixed(1) + ' miles)'}
              </li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}

Frontend: The React Component (ZipCodeRadiusSearch.tsx)

This client component manages the state for the form inputs (ZIP code and radius), loading status, errors, and search results. When the form is submitted, it makes a fetch request to our own Next.js API route. It then displays the loading state, any errors, or the list of returned ZIP codes.

Backend: The Next.js API Route (app/api/zip-radius/route.ts)

This server-side route acts as a secure proxy. It receives the request from our React component, extracts the search parameters, and then constructs a new request to the actual Payloadic ZIP Code API, injecting the secret API key from environment variables. This ensures your API key is never exposed to the public. It then forwards the response from the ZIP Code API back to the client.