I have built an AddressSearch page but the behaviour is not as expected.
How does it work?
There is a search input field in which user enters the address and Google Maps JS API suggest some options based on the search, user selects on of them.
Then an API call goes to an endpoint with payload like this and the data we get back, we show on UI.
{ "searchBy": "address", "address": { "street": "", "postalCode": "", "city": "", "state": "CA", "country": "US", "formattedAddress": "California, USA" }, "page": 2, "size": 20, "sortBy": "id", "minimumStage": 4 }So, we pass the page field and size field and in return we get list of systems to show on UI in a row.
There are three actions that take place -
Change address → All the data gets cleared on UI, search happens with the new address, and data gets shown on UI.
Change sort direction → All the data gets cleared on UI, search happens with new sort direction and data gets shown on UI.
Scroll to the bottom of the page → New API gets called with the page number increased and the data gets appended to the existing data.
This is the codebase:
export const useSystemAddressStrategy = ({ companyId, gMaps, initialAddress, }: SystemAddressStrategyOptions): StrategyResult => {// ----- Address const [addressParams, setAddressParams] = useState< AdvancedSearchAddressPayload & { formattedAddress: string } >(convertToAddressPayload(initialAddress)); // ----- Page const [page, setPage] = useState<number>(0); // ----- Sort Direction const [sortDirection, setSortDirection] = useState<SortDirection>(SortDirection.ASC); // ----- Number of filtered rows for that address
// --------- Derived params const sortBy = sortDirection === SortDirection.ASC ? 'id' : '-id';
// ----- Rows to display on screen const [accumulatedRows, setAccumulatedRows] = useState<EnphaseSystemTableRow[]>([]);
const searchParams = useMemo( () => ({ searchBy: 'address' as const, address: addressParams, page: page + 1, size: 20, sortBy, }), [addressParams, page, sortDirection] );
const renderId = React.useRef(0); renderId.current += 1;
console.log('RENDER', renderId.current, { address: addressParams.formattedAddress, page, });
console.log('QUERY INPUT', { address: searchParams.address.formattedAddress, pageState: page, apiPage: searchParams.page, });
// ----- Query const query = useBrowseEnphaseSystems(companyId, searchParams); const latestFetchedRows: EnphaseSystemTableRow[] = React.useMemo(() => { const responseData: EnphaseSystemsResponse | undefined = query.data; if (responseData?.systems && responseData.systems.length) { return responseData.systems.map((system) => ({ id: String(system.systemId), systemId: String(system.systemId), systemName: String(system.systemName) || '-', address: system.address || {}, ownerName: String(system.ownerName || '-'), timezone: system.timezone, lastReportedAt: system.lastReportedAt, operationalAt: system.operationalAt, })); } return []; }, [query.data]); const { error } = query as { error?: ErrorWithCode }; const isInvalidTokenError = isEnlightenInvalidTokenError(error);
// ---- Helper functions const hasNextPage = () => accumulatedRows.length < (query?.data?.filteredTotal ?? 0); const hasMore = hasNextPage(); const isFetchingInitialData = query.isFetching && accumulatedRows.length === 0;
// --- Actions const fetchNextPage = () => { console.log('fetchNextPage() called'); if (!hasMore || query.isFetching) return; console.log('updating page, before updating, page: ', page); setPage((prev) => prev + 1); };
const handleAddressChange = (newAddress: EnphaseAddressParams) => { console.log('HANDLE ADDRESS CHANGE', { address: newAddress.formattedAddress, currentPage: page, });
setAddressParams(convertToAddressPayload(newAddress)); setPage(0); setAccumulatedRows([]); };
const handleSortChange = useCallback((_columnId: string, direction: SortDirection) => { setSortDirection(direction); setPage(0); setAccumulatedRows([]); }, []);
useEffect(() => { if (latestFetchedRows.length === 0) return; setAccumulatedRows((prev) => [...prev, ...latestFetchedRows]); }, [latestFetchedRows]);
return { searchText: '', addressText: addressParams.formattedAddress, accumulatedRows, sortDirection, isFetching: query.isFetching, isError: query.isError, isInvalidTokenError, isFetchingInitialData, hasSearchInput: Boolean(addressParams.formattedAddress), hasMore, handleSearchTextChange: () => {}, handleAddressChange, fetchNextPage, handleSortChange, reset: () => {}, }; };
The issue is happening in this case →
First, I search "California, US" and scroll down → I see three API calls as expected and 43 rows I see on UI.
Then I search "Albama, US" and now
handleAddressChange()gets called(as expected).
But I see two API calls - with these payloads →{ "searchBy": "address", "address": { "street": "", "postalCode": "", "city": "", "state": "AL", "country": "US", "formattedAddress": "Alabama, USA" }, "page": 3, "size": 20, "sortBy": "id", "minimumStage": 4 }and
{ "searchBy": "address", "address": { "street": "", "postalCode": "", "city": "", "state": "AL", "country": "US", "formattedAddress": "Alabama, USA" }, "page": 1, "size": 20, "sortBy": "id", "minimumStage": 4 }where as I only expect one call with the second payload.
I'm totally confused on why the second call is happening. I did console log and this is what it showed
HANDLE ADDRESS CHANGE {address: 'Alabama, USA', currentPage: 0}
RENDER 34 {address: 'Alabama, USA', page: 2}
QUERY INPUT {address: 'Alabama, USA', pageState: 2, apiPage: 3}
RENDER 35 {address: 'Alabama, USA', page: 0}
QUERY INPUT {address: 'Alabama, USA', pageState: 0, apiPage: 1}
RENDER 36 {address: 'Alabama, USA', page: 0}I don't understand the behaviour → first log printed the updated address and page as
0. Then why again the next log printed page as2?I understand
handleAddressChange()is the reason behind this - the state updates in it.
But I don't understand why this behaviour and how to deal with this. I've had multiple conversations with Opus and ChatGPT but couldn't make it work.