[fix] correct city names in address autocomplete dropdown#29
[fix] correct city names in address autocomplete dropdown#29
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. To trigger a review, include ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Comment |
| }).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; |
There was a problem hiding this comment.
🔴 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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?
.context/todos.md
Outdated
There was a problem hiding this comment.
what are these two empty files?
| // 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; |
There was a problem hiding this comment.
why do we think this is better / more accurate? longer length doesnt represent more information necessarily right?
There was a problem hiding this comment.
We should always honor / respect user's input.
| }).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; |
There was a problem hiding this comment.
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)
| 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, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
why only overwrite city? why not use the entire corrected address?
There was a problem hiding this comment.
b/c the only discrepancy I have noticed is with city. Have we seen any other discrepancy / should we prefer if we swap everything?
.context/todos.md
Outdated
| }).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; |
There was a problem hiding this comment.
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?
| 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, | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
b/c the only discrepancy I have noticed is with city. Have we seen any other discrepancy / should we prefer if we swap everything?
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>
e974dcb to
d79c99b
Compare
|
|
||
| useEffect(() => { | ||
| if (!places) return; | ||
| if (!places || !addressValidation) return; |
There was a problem hiding this comment.
🔴 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.
Was this helpful? React with 👍 or 👎 to provide feedback.
|
|
||
| try { | ||
| const validation = | ||
| await addressValidation.AddressValidation.fetchAddressValidation( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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

Summary
secondaryTextreturns Census Designated Place names (e.g. "Briarcliff, TX") instead of USPS postal cities (e.g. "Austin, TX")enableUspsCass: trueto get USPS-verified city namesBefore

What should be

After

Prerequisite: Address Validation API must be enabled on the Google Cloud project. prod-bpc has address validation API enabled
Test plan
🤖 Generated with Claude Code