Skip to content

[fix] correct city names in address autocomplete dropdown#29

Open
ruchirx wants to merge 10 commits intomainfrom
ruchirx/fix-city-validation
Open

[fix] correct city names in address autocomplete dropdown#29
ruchirx wants to merge 10 commits intomainfrom
ruchirx/fix-city-validation

Conversation

@ruchirx
Copy link

@ruchirx ruchirx commented Mar 5, 2026

Summary

  • Google Places Autocomplete secondaryText returns Census Designated Place names (e.g. "Briarcliff, TX") instead of USPS postal cities (e.g. "Austin, TX")
  • After fetching autocomplete suggestions, calls the Address Validation API with enableUspsCass: true to get USPS-verified city names
  • Passes the longer of user input vs autocomplete street address to the API (so user-provided city/state info is preserved when available)
  • USPS city names are title-cased for display (API returns uppercase)

Before
Screenshot 2026-03-04 at 3 56 55 PM

What should be
Screenshot 2026-03-04 at 3 57 07 PM

After
Screenshot 2026-03-04 at 6 39 04 PM

Prerequisite: Address Validation API must be enabled on the Google Cloud project. prod-bpc has address validation API enabled

Test plan

  • Search "6200 Hilltop Plateau Dr" — dropdown should show "Austin, TX, USA" not "Briarcliff, TX, USA"
  • Search a normal address (e.g. "1100 Congress Ave") — should still show correct city
  • Select an address — input field and parsed result should have correct city
  • Verify no visual flicker (suggestions appear with correct city atomically)

🤖 Generated with Claude Code


Open with Devin

@coderabbitai
Copy link

coderabbitai bot commented Mar 5, 2026

Important

Review skipped

Auto reviews are disabled on this repository. To trigger a review, include rabbit in the PR description. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a49ef7a8-7c5d-4aee-9bb7-1364e04f95f0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ruchirx/fix-city-validation

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Comment @coderabbitai help to get the list of available commands and usage tips.

@ruchirx ruchirx requested a review from divazbozz March 5, 2026 00:44
devin-ai-integration[bot]

This comment was marked as resolved.

@ruchirx ruchirx marked this pull request as draft March 5, 2026 03:27
@ruchirx ruchirx marked this pull request as ready for review March 5, 2026 16:15
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 6 additional findings in Devin Review.

Open in Devin Review

Comment on lines +67 to 133
}).then(async ({ suggestions }) => {
suggestions.forEach((suggestion) => {
if (!suggestion.placePrediction?.placeId) return;
placesRef.current[suggestion.placePrediction.placeId] =
suggestion;
});

// Use Address Validation API to get correct USPS city names.
// Autocomplete secondaryText returns CDPs like "Briarcliff"
// instead of the postal city like "Austin".
await Promise.all(
suggestions.map(async (suggestion) => {
const placeId = suggestion.placePrediction?.placeId;
const streetAddress =
suggestion.placePrediction?.mainText?.text;
if (
!placeId ||
!streetAddress ||
correctedTextRef.current[placeId]
)
return;

// Use whichever has more info: user's input (may include
// city/state) or the autocomplete street address
const addressInput =
searchQuery.length > streetAddress.length
? searchQuery
: streetAddress;

try {
const { AddressValidation } =
await google.maps.importLibrary("addressValidation");
const validation =
await AddressValidation.fetchAddressValidation({
address: {
addressLines: [addressInput],
regionCode: "US",
},
uspsCASSEnabled: true,
});

const uspsCity =
validation.uspsData?.standardizedAddress?.city;
const postalAddress = validation.address?.postalAddress;
const rawCity = uspsCity || postalAddress?.locality || "";
// USPS city is uppercase (e.g. "AUSTIN"), title-case it
const city = rawCity
.toLowerCase()
.replace(/\b\w/g, (c: string) => c.toUpperCase());
const state = postalAddress?.administrativeArea || "";
const country =
postalAddress?.regionCode === "US"
? "USA"
: (postalAddress?.regionCode ?? "");

if (city) {
correctedTextRef.current[placeId] = [city, state, country]
.filter(Boolean)
.join(", ");
}
} catch {
// Fall back to default secondaryText on error
}
}),
);

return suggestions;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Autocomplete suggestions are blocked until all Address Validation API calls complete

The promise stored in the cache state now includes await Promise.all(...) for N address validation calls (one per suggestion) inside the .then(async ...) handler at lines 67-133. Since .then(async fn) returns a new promise that doesn't resolve until the async function completes, the second useEffect (line 147-150) that calls cached.then(suggestions => setPlacesResult(suggestions)) won't fire until every validation call finishes. Previously, autocomplete results appeared as soon as fetchAutocompleteSuggestions returned. Now they're delayed by the latency of all validation API calls (typically 5 parallel requests), significantly degrading the user experience for type-ahead search.

Prompt for agents
In src/address-search/AddressSearch.tsx, the address validation calls should not block the autocomplete suggestions from appearing. The fix is to separate the address validation from the cached promise so that suggestions display immediately.

One approach: In the .then() callback (lines 67-133), remove the `async` keyword and the `await Promise.all(...)` block from the promise chain. Instead, fire the address validation calls as a side effect (without awaiting them), and when they complete, trigger a re-render so the corrected text is picked up. For example:

1. Change line 67 from `.then(async ({ suggestions }) => {` to `.then(({ suggestions }) => {`
2. Remove the `await` before `Promise.all(...)` on line 77. Instead, use the Promise.all(...).then(() => { /* trigger re-render somehow */ }) pattern.
3. Since correctedTextRef is a ref and doesn't trigger re-renders, you need a mechanism to re-render after validation completes. Options include: (a) store corrected text in state instead of a ref, (b) use a counter state that increments after validation completes to force useMemo recalculation, or (c) call setPlacesResult with the same suggestions again after validation to trigger useMemo.

The simplest approach would be to keep a state-based trigger: add a `const [validationVersion, setValidationVersion] = useState(0)` and increment it when validation calls complete. Include `validationVersion` in the useMemo dependency array at line 201. Then the Promise.all can run in the background and call setValidationVersion(v => v+1) on completion.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ⏫ , this component is pretty important in terms of how snappy it feels, can we double check latency / user experience (can we show a video or sth to see how it feels)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried this on npm run dev, and it feels fast and snappy enough.

unclear whats ask here, Do you want me to record a video and upload somewhere as evidence?

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these two empty files?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure. removed.

Comment on lines +89 to +94
// Use whichever has more info: user's input (may include
// city/state) or the autocomplete street address
const addressInput =
searchQuery.length > streetAddress.length
? searchQuery
: streetAddress;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we think this is better / more accurate? longer length doesnt represent more information necessarily right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should always honor / respect user's input.

Comment on lines +67 to 133
}).then(async ({ suggestions }) => {
suggestions.forEach((suggestion) => {
if (!suggestion.placePrediction?.placeId) return;
placesRef.current[suggestion.placePrediction.placeId] =
suggestion;
});

// Use Address Validation API to get correct USPS city names.
// Autocomplete secondaryText returns CDPs like "Briarcliff"
// instead of the postal city like "Austin".
await Promise.all(
suggestions.map(async (suggestion) => {
const placeId = suggestion.placePrediction?.placeId;
const streetAddress =
suggestion.placePrediction?.mainText?.text;
if (
!placeId ||
!streetAddress ||
correctedTextRef.current[placeId]
)
return;

// Use whichever has more info: user's input (may include
// city/state) or the autocomplete street address
const addressInput =
searchQuery.length > streetAddress.length
? searchQuery
: streetAddress;

try {
const { AddressValidation } =
await google.maps.importLibrary("addressValidation");
const validation =
await AddressValidation.fetchAddressValidation({
address: {
addressLines: [addressInput],
regionCode: "US",
},
uspsCASSEnabled: true,
});

const uspsCity =
validation.uspsData?.standardizedAddress?.city;
const postalAddress = validation.address?.postalAddress;
const rawCity = uspsCity || postalAddress?.locality || "";
// USPS city is uppercase (e.g. "AUSTIN"), title-case it
const city = rawCity
.toLowerCase()
.replace(/\b\w/g, (c: string) => c.toUpperCase());
const state = postalAddress?.administrativeArea || "";
const country =
postalAddress?.regionCode === "US"
? "USA"
: (postalAddress?.regionCode ?? "");

if (city) {
correctedTextRef.current[placeId] = [city, state, country]
.filter(Boolean)
.join(", ");
}
} catch {
// Fall back to default secondaryText on error
}
}),
);

return suggestions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this ⏫ , this component is pretty important in terms of how snappy it feels, can we double check latency / user experience (can we show a video or sth to see how it feels)

Comment on lines +182 to +192
const correctedCity = corrected.split(",")[0].trim();
if (correctedCity) {
const originalCity = selection.address.city;
selection.address.city = correctedCity;
if (originalCity) {
selection.formattedAddress = selection.formattedAddress.replace(
originalCity,
correctedCity,
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only overwrite city? why not use the entire corrected address?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b/c the only discrepancy I have noticed is with city. Have we seen any other discrepancy / should we prefer if we swap everything?

Copy link
Author

@ruchirx ruchirx left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure. removed.

Comment on lines +67 to 133
}).then(async ({ suggestions }) => {
suggestions.forEach((suggestion) => {
if (!suggestion.placePrediction?.placeId) return;
placesRef.current[suggestion.placePrediction.placeId] =
suggestion;
});

// Use Address Validation API to get correct USPS city names.
// Autocomplete secondaryText returns CDPs like "Briarcliff"
// instead of the postal city like "Austin".
await Promise.all(
suggestions.map(async (suggestion) => {
const placeId = suggestion.placePrediction?.placeId;
const streetAddress =
suggestion.placePrediction?.mainText?.text;
if (
!placeId ||
!streetAddress ||
correctedTextRef.current[placeId]
)
return;

// Use whichever has more info: user's input (may include
// city/state) or the autocomplete street address
const addressInput =
searchQuery.length > streetAddress.length
? searchQuery
: streetAddress;

try {
const { AddressValidation } =
await google.maps.importLibrary("addressValidation");
const validation =
await AddressValidation.fetchAddressValidation({
address: {
addressLines: [addressInput],
regionCode: "US",
},
uspsCASSEnabled: true,
});

const uspsCity =
validation.uspsData?.standardizedAddress?.city;
const postalAddress = validation.address?.postalAddress;
const rawCity = uspsCity || postalAddress?.locality || "";
// USPS city is uppercase (e.g. "AUSTIN"), title-case it
const city = rawCity
.toLowerCase()
.replace(/\b\w/g, (c: string) => c.toUpperCase());
const state = postalAddress?.administrativeArea || "";
const country =
postalAddress?.regionCode === "US"
? "USA"
: (postalAddress?.regionCode ?? "");

if (city) {
correctedTextRef.current[placeId] = [city, state, country]
.filter(Boolean)
.join(", ");
}
} catch {
// Fall back to default secondaryText on error
}
}),
);

return suggestions;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried this on npm run dev, and it feels fast and snappy enough.

unclear whats ask here, Do you want me to record a video and upload somewhere as evidence?

Comment on lines +182 to +192
const correctedCity = corrected.split(",")[0].trim();
if (correctedCity) {
const originalCity = selection.address.city;
selection.address.city = correctedCity;
if (originalCity) {
selection.formattedAddress = selection.formattedAddress.replace(
originalCity,
correctedCity,
);
}
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

b/c the only discrepancy I have noticed is with city. Have we seen any other discrepancy / should we prefer if we swap everything?

Ruchir Patel and others added 10 commits March 9, 2026 11:30
Google Places Autocomplete returns CDP names like "Briarcliff, TX"
instead of the USPS postal city "Austin, TX". After fetching
autocomplete suggestions, call the Address Validation API with
enableUspsCass to get USPS-verified city names for the dropdown.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…I key

Move Address Validation API call behind /api/validate-address backend
endpoint. The backend holds the API key server-side, preventing exposure
in client-side code. Removes apiKey prop threading through components.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use google.maps.importLibrary("addressValidation") with
AddressValidation.fetchAddressValidation() — same SDK pattern as
Places autocomplete. No backend proxy needed, no API key in client code.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ruchirx ruchirx force-pushed the ruchirx/fix-city-validation branch from e974dcb to d79c99b Compare March 9, 2026 16:30
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 12 additional findings in Devin Review.

Open in Devin Review


useEffect(() => {
if (!places) return;
if (!places || !addressValidation) return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Autocomplete completely blocked until addressValidation library finishes loading

At line 46, the guard if (!places || !addressValidation) return; prevents any autocomplete requests from firing until the addressValidation library has loaded. Previously, autocomplete only needed places to be available. Since addressValidation is a separate library loaded asynchronously via useMapsLibrary (src/utils/useMapsLibrary.ts:27-30), this adds latency before any suggestions can appear. Worse, if the addressValidation library fails to load (e.g., not enabled on the API key, network error), autocomplete will be permanently non-functional.

The address validation calls are an enhancement (correcting city names) and should degrade gracefully — the autocomplete suggestions should still be shown even if validation hasn't loaded yet.

Prompt for agents
In src/address-search/AddressSearch.tsx, line 46, change the guard back to only require `places` (not `addressValidation`). The address validation enhancement should be optional/graceful. Specifically:

1. Change line 46 from `if (!places || !addressValidation) return;` to `if (!places) return;`
2. Inside the `.then(async ({ suggestions }) => { ... })` callback (around lines 78-132), wrap the address validation `Promise.all` block in a check: `if (addressValidation) { ... }`. If `addressValidation` is not loaded yet, just skip the validation step and return suggestions immediately.
3. Make sure the `addressValidation` dependency is still in the useEffect deps array (line 138) so that when it loads later, it can trigger re-fetching of validation data for queries that were already cached without validation.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@divazbozz
Copy link
Contributor

i tested a much smaller changes without sacrificing 5x API call and can get the desired results

image

by

					places.AutocompleteSuggestion.fetchAutocompleteSuggestions({
						input: searchQuery,
						sessionToken: curToken,
						// region: "US", // Don't restrict to US -- this changes the way the formatted address is returned
						language: "en",
						// includedPrimaryTypes: ["street_address"],  <----- this was the change
					})

i need to understand it a bit on why the differences cause by that field, but i think this PR introduces performance penalty and complexity while there probably is a simpler approach. wdyt

@divazbozz
Copy link
Contributor


try {
const validation =
await addressValidation.AddressValidation.fetchAddressValidation(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is gonna get extremely expensive. Rough estimate is probably at least 10x our cost. We should find a better solution here or only fetch validation after selection.

Copy link

@ruchir-bpc ruchir-bpc Mar 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack, @divazbozz looks like has a better solution. If so, i'll be happy to close this one.

ref:
https://github.com/BasePowerCompany/bpc-web-components/pull/31/changes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants