NJ Municipality Lookup
CodebaseSrcApp(app)

Lookup

Single address lookup page with real-time autocomplete suggestions and municipality identification.

Address Lookup Page (app/lookup/)

Single address lookup page with real-time autocomplete suggestions and municipality identification.

Purpose

Primary user interface for geocoding individual addresses:

  1. Accept address input with autocomplete
  2. Validate and geocode address via NJ OGIS API
  3. Display municipality, county, and formatted address
  4. Show coordinates and cache status

Route

/lookup - Single Address Lookup

Features

Auto-Suggestions

  • Real-time: Suggestions appear as user types
  • Minimum Characters: 2+ characters to trigger suggestions
  • Debounced: 300ms delay to reduce API calls
  • Keyboard Navigation: Arrow keys + Enter to select
  • Click Selection: Mouse click to select suggestion

Address Input

  • Structured Form: Separate fields for street, city, ZIP
  • Validation: Client-side validation before submission
  • Error Messages: Inline errors for invalid input
  • XSS Protection: Input sanitization via validation pipeline

Results Display

When geocoding succeeds, display:

  • Municipality Name: e.g., "Newark"
  • County: e.g., "Essex County"
  • Municipality Type: e.g., "City"
  • Formatted Address: Normalized NJ state format
  • Coordinates: Latitude/longitude
  • Cache Status: Hit/miss indicator

Error Handling

User-friendly error messages for:

  • Address Not Found: "We couldn't find this address..."
  • Validation Error: "Please enter a valid street address"
  • API Timeout: "The service is taking too long to respond..."
  • Network Error: "Connection failed. Check your internet..."

Usage Flow

  1. User navigates to /lookup
  2. User types in street address field
  3. Suggestions appear (after 2+ characters)
  4. User selects suggestion or continues typing
  5. User fills city and ZIP fields
  6. User clicks "Lookup" button
  7. Loading spinner appears
  8. Results or error message displayed

Form Fields

Street Address (Required)

  • Type: Text input with autocomplete
  • Validation: Non-empty, max 500 characters, no XSS
  • Autocomplete: Triggers after 2+ characters
  • Example: "323 Dr Martin Luther King Jr Blvd"

City (Required)

  • Type: Text input
  • Validation: Non-empty, max 100 characters
  • Example: "Newark"

ZIP Code (Optional)

  • Type: Text input (numeric)
  • Validation: 5 digits (if provided)
  • Example: "07102"

Server Actions

Page calls these server actions:

import { geocodeAddress, getAddressSuggestions } from "@/app/actions/geocoding";

// Get autocomplete suggestions
const suggestions = await getAddressSuggestions("323 Dr", 5);

// Geocode full address
const result = await geocodeAddress(
  "323 Dr Martin Luther King Jr Blvd, Newark, NJ 07102",
);

UI Components

<AddressLookupForm onSubmit={handleSubmit} loading={isPending} error={error} />;

{
  result && <GeocodingResults result={result} />;
}

{
  error && <ErrorMessage message={error} />;
}

State Management

Lookup form manages:

interface LookupState {
  // Input
  streetAddress: string;
  city: string;
  zipCode: string;

  // Suggestions
  suggestions: AddressSuggestion[];
  suggestionsVisible: boolean;

  // Processing
  loading: boolean;
  error: string | null;

  // Result
  result: GeocodingResult | null;
  cacheHit: boolean;
}

Cache Behavior

Results displayed with cache indicator:

  • Cache Hit (< 50ms): "✅ Cached result (fast)"
  • Cache Miss (200-500ms): "🌐 Fetched from API"

Accessibility

  • Skip Link: Skip to main content
  • Form Labels: Descriptive labels for all inputs
  • Error Association: ARIA-described by for error messages
  • Focus Management: Focus moves to results after submission
  • Keyboard Navigation: Tab through all interactive elements
  • Screen Reader: Status announcements for loading/results

Responsive Design

  • Mobile: Stacked fields, full-width buttons
  • Tablet: 2-column layout for city/ZIP
  • Desktop: Wide form with autocomplete dropdown

Testing

Lookup page is tested with:

  1. Unit Tests: Component rendering and state management
  2. Integration Tests: Server action integration
  3. E2E Tests: Playwright tests for full user flow

E2E Test Example:

test("should display municipality on successful lookup", async ({ page }) => {
  await page.goto("/lookup");

  // Fill form
  await page
    .getByRole("textbox", { name: /street/i })
    .fill("323 Dr Martin Luther King Jr Blvd");
  await page.getByRole("textbox", { name: /city/i }).fill("Newark");
  await page.getByRole("textbox", { name: /zip/i }).fill("07102");

  // Submit
  await page.getByRole("button", { name: /lookup/i }).click();

  // Verify results
  await expect(page.getByText(/municipality/i)).toBeVisible();
  await expect(page.getByText(/newark/i)).toBeVisible();
  await expect(page.getByText(/essex/i)).toBeVisible();
});

Performance

  • Suggestions: < 500ms (debounced, cached)
  • Cache Hit: < 50ms
  • Cache Miss: 200-500ms (depends on NJ API)
  • Client Bundle: Minimal JS (React Server Components)

Example Usage

Successful Lookup

Input:

  • Street: "323 Dr Martin Luther King Jr Blvd"
  • City: "Newark"
  • ZIP: "07102"

Output:

Municipality: Newark
County: Essex County
Type: City
Formatted Address: 323 DR MARTIN LUTHER KING JR BLVD, NEWARK, NJ, 07102
Coordinates: 40.740623, -74.175383
Status:Cached result

Address Not Found

Input:

  • Street: "123 Nonexistent St"
  • City: "Nowhere"
  • ZIP: "00000"

Output:

Address not found
We couldn't find this address in New Jersey. Please check:
- Street number and name are correct
- City name is spelled correctly
- Address is located in New Jersey

On this page