Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';

import {updateGpsPoints, updateTrimmedEndPoint} from '@libs/actions/GPSDraftDetails';
import {addressFromGpsPoint, getGpsPoints} from '@libs/GPSDraftDetailsUtils';

import ONYXKEYS from '@src/ONYXKEYS';
import type {GPSPoint, TrimmedGPSPoint} from '@src/types/onyx/GpsDraftDetails';

import OnyxUtils from 'react-native-onyx/dist/OnyxUtils';
import {useEffect, useRef} from 'react';

function useUpdateGpsTripOnReconnect({gpsPoints}: {gpsPoints: GPSPoint[][]}) {
const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Gate reconnect writes on draft hydration

If the app starts offline with a persisted GPS trip and reconnects before this subscription reaches loaded, both gpsDraftDetails and the parent-provided fallback are initially empty. The empty Promise.all then completes immediately and updateGpsPoints([[]]) overwrites the persisted route; the parent checker already guards its startup logic with gpsDraftDetailsMetadata.status !== 'loaded' for this reason. Read the metadata here and defer the reconnect handler until the draft has hydrated.

Useful? React with 👍 / 👎.

@mkhutornyi mkhutornyi Aug 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Edge case enough to ignore. Not reproducible.

// Mirror the latest gpsDraftDetails into a ref so the async onReconnect handler can read the newest value
// after awaiting reverse geocoding, instead of the stale value captured when the callback started.
const latestGpsDraftDetailsRef = useRef(gpsDraftDetails);
useEffect(() => {
latestGpsDraftDetailsRef.current = gpsDraftDetails;
}, [gpsDraftDetails]);
Comment on lines +17 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve an authoritative post-geocoding draft read

When GPS_DRAFT_DETAILS changes while reverse geocoding is pending, the Onyx notification can schedule a render without this passive effect having run before the promise continuation. In that window the ref still contains the previous route, so the later updateGpsPoints() replaces points recorded during geocoding—the race the removed post-await OnyxUtils.get() avoided. The merge needs an authoritative latest-value read or an atomic state update rather than relying on effect timing.

Useful? React with 👍 / 👎.

@mkhutornyi mkhutornyi Aug 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Edge case enough to ignore. Not reproducible


// The trimmed end point is chosen in the Edit Stop screen. When trimmed while offline, its address is stored as
// stringified coordinates, so on reconnect we fetch the human readable address to replace it.
const updateTrimmedEndPointAddress = async (trimmedEndPoint: TrimmedGPSPoint | undefined) => {
Expand Down Expand Up @@ -57,9 +66,8 @@ function useUpdateGpsTripOnReconnect({gpsPoints}: {gpsPoints: GPSPoint[][]}) {

const waypointAddresses = (await Promise.all(waypointUpdates)).filter((waypoints) => !!waypoints.point.address);

// To avoid race conditions, we need to get the latest gpsDraftDetails, because reverse geocoding may even take a few seconds
const gpsDraftDetailsPromiseResult = await OnyxUtils.get(ONYXKEYS.GPS_DRAFT_DETAILS).catch(() => undefined);
const latestGpsDraftDetails = gpsDraftDetailsPromiseResult;
// To avoid race conditions, we need the latest gpsDraftDetails, because reverse geocoding may even take a few seconds
const latestGpsDraftDetails = latestGpsDraftDetailsRef.current;

const latestGpsPoints = getGpsPoints(latestGpsDraftDetails) ?? gpsPoints;
const newGpsPoints = [...latestGpsPoints];
Expand Down
Loading