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:
- Accept address input with autocomplete
- Validate and geocode address via NJ OGIS API
- Display municipality, county, and formatted address
- 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
- User navigates to
/lookup - User types in street address field
- Suggestions appear (after 2+ characters)
- User selects suggestion or continues typing
- User fills city and ZIP fields
- User clicks "Lookup" button
- Loading spinner appears
- 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:
- Unit Tests: Component rendering and state management
- Integration Tests: Server action integration
- 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
Related Documentation
- Geocoding Server Actions - Form submission logic
- Address Lookup Components - UI components
- Address Components - Input fields with validation
- Use Cases - Business logic