Skip to content

feat: expose entryId and entryKey from Navigation API in useLocation#151

Merged
uhyo merged 5 commits intomasterfrom
claude/expose-navigation-api-data-tEVgT
Mar 12, 2026
Merged

feat: expose entryId and entryKey from Navigation API in useLocation#151
uhyo merged 5 commits intomasterfrom
claude/expose-navigation-api-data-tEVgT

Conversation

@uhyo
Copy link
Owner

@uhyo uhyo commented Mar 12, 2026

Add NavigationHistoryEntry.id and NavigationHistoryEntry.key to the
Location type returned by useLocation(). These are exposed as entryId
and entryKey respectively, and are null when the Navigation API is
unavailable (e.g., static fallback mode).

https://claude.ai/code/session_014uqruYL8PktVwDVrSA6HJQ

claude added 4 commits March 12, 2026 00:21
Add NavigationHistoryEntry.id and NavigationHistoryEntry.key to the
Location type returned by useLocation(). These are exposed as entryId
and entryKey respectively, and are null when the Navigation API is
unavailable (e.g., static fallback mode).

https://claude.ai/code/session_014uqruYL8PktVwDVrSA6HJQ
entryId is unique per entry and changes on replacement. entryKey
represents the slot and is stable across replacements.

Also warns against rendering these values in DOM since they are
unavailable during SSR.

https://claude.ai/code/session_014uqruYL8PktVwDVrSA6HJQ
Use the same pattern as locationKey to inspect the server snapshot's
actual location entry during the first hydration render.

https://claude.ai/code/session_014uqruYL8PktVwDVrSA6HJQ
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the router’s useLocation() API to expose Navigation API NavigationHistoryEntry.id and .key via two new Location fields: entryId and entryKey, using null when the Navigation API isn’t available (e.g., fallback/static mode).

Changes:

  • Extend Location / router context and adapter snapshot types to include entryId and entryKey.
  • Plumb entryId/entryKey from adapters → RouterContextuseLocation().
  • Add docs and a hook test asserting entryId/entryKey are present under the Navigation API mock.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/router/src/types.ts Adds entryId/entryKey to the public Location type with SSR/hydration warnings.
packages/router/src/hooks/useLocation.ts Returns entryId/entryKey from RouterContext and memoizes with updated deps.
packages/router/src/core/StaticAdapter.ts Sets entryId/entryKey to null in static fallback snapshots.
packages/router/src/core/RouterAdapter.ts Extends internal LocationEntry snapshot type with entryId/entryKey.
packages/router/src/core/NavigationAPIAdapter.ts Populates snapshot entryId/entryKey from navigation.currentEntry.
packages/router/src/context/RouterContext.ts Adds entryId/entryKey to the context value contract.
packages/router/src/tests/hooks.test.tsx Adds a test for useLocation() returning entryId/entryKey under Navigation API mock.
packages/router/src/Router/index.tsx Plumbs entryId/entryKey into RouterContext from the current location entry.
packages/docs/src/pages/ApiTypesPage.tsx Documents the new Location.entryId/entryKey fields and SSR warning.
packages/docs/src/pages/ApiHooksPage.tsx Documents useLocation() exposing entryId/entryKey and SSR warning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +50 to +70
it("returns entryId and entryKey from Navigation API", () => {
function TestComponent() {
const location = useLocation();
return (
<div>
<span data-testid="entryId">{location.entryId ?? "null"}</span>
<span data-testid="entryKey">{location.entryKey ?? "null"}</span>
</div>
);
}

const routes: RouteDefinition[] = [
{ path: "/", component: TestComponent },
];

render(<Router routes={routes} />);

// The mock navigation generates UUIDs for id and key
expect(screen.getByTestId("entryId").textContent).not.toBe("null");
expect(screen.getByTestId("entryKey").textContent).not.toBe("null");
});
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Test coverage currently only asserts that entryId/entryKey are non-null with the Navigation API mock. Since the public contract says these are null when the Navigation API is unavailable and during SSR/hydration, it would be good to add assertions for those cases as well (e.g., static fallback mode and an SSR+hydrate scenario) to prevent regressions—especially given the hydration-mismatch warning in the docs.

Copilot uses AI. Check for mistakes.
Comment on lines +266 to +271
const entryId =
locationEntry?.entryId ??
(isServerSnapshot(locationEntryInternal)
? locationEntryInternal.actualLocationEntry?.entryId
: null) ??
null;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

entryId is being populated from ServerLocationSnapshot.actualLocationEntry during hydration (when locationEntryInternal is a server snapshot). That makes entryId non-null on the first client render even though it was null on the server, which violates the documented “null during SSR/hydration” contract and can trigger hydration/key mismatches. Consider returning null for entryId while isServerSnapshot(locationEntryInternal) (and only exposing it once locationEntryInternal has synced to the real client snapshot).

Suggested change
const entryId =
locationEntry?.entryId ??
(isServerSnapshot(locationEntryInternal)
? locationEntryInternal.actualLocationEntry?.entryId
: null) ??
null;
const entryId = isServerSnapshot(locationEntryInternal)
? null
: locationEntry?.entryId ?? null;

Copilot uses AI. Check for mistakes.
Comment on lines +272 to +277
const entryKey =
locationEntry?.entryKey ??
(isServerSnapshot(locationEntryInternal)
? locationEntryInternal.actualLocationEntry?.entryKey
: null) ??
null;
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

Same issue as entryId: entryKey is read from ServerLocationSnapshot.actualLocationEntry during hydration, so it can differ between server HTML and the first client render. Since this value is explicitly documented as unavailable during SSR, it should stay null while locationEntryInternal is a server snapshot to avoid hydration/key mismatches.

Suggested change
const entryKey =
locationEntry?.entryKey ??
(isServerSnapshot(locationEntryInternal)
? locationEntryInternal.actualLocationEntry?.entryKey
: null) ??
null;
const entryKey = isServerSnapshot(locationEntryInternal)
? null
: locationEntry?.entryKey ?? null;

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +143
* Null when Navigation API is unavailable.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Use it as a
* React `key` or in effects/callbacks instead.
*/
entryId: string | null;
/**
* NavigationHistoryEntry.key — represents the slot in the entry list.
* Stable across replacements.
* Null when Navigation API is unavailable.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Use it as a
* React `key` or in effects/callbacks instead.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The docstring says “Null when Navigation API is unavailable”, but the warning immediately below references SSR/hydration. Consider updating the main description to also state “null during SSR/hydration”, and reconsider the suggestion to “Use it as a React key”: keys participate in reconciliation/hydration, so a value that’s null on the server and non-null on the client can still cause hydration issues even if it’s not rendered to the DOM.

Suggested change
* Null when Navigation API is unavailable.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Use it as a
* React `key` or in effects/callbacks instead.
*/
entryId: string | null;
/**
* NavigationHistoryEntry.key represents the slot in the entry list.
* Stable across replacements.
* Null when Navigation API is unavailable.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Use it as a
* React `key` or in effects/callbacks instead.
* Null when Navigation API is unavailable or during SSR/hydration.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Avoid using it
* as a React `key` in SSR/hydrated trees; prefer using it only in
* effects/callbacks or other client-only logic instead.
*/
entryId: string | null;
/**
* NavigationHistoryEntry.key represents the slot in the entry list.
* Stable across replacements.
* Null when Navigation API is unavailable or during SSR/hydration.
*
* **Warning:** Do not render this value directly in DOM, as it is not
* available during SSR and will cause a hydration mismatch. Avoid using it
* as a React `key` in SSR/hydrated trees; prefer using it only in
* effects/callbacks or other client-only logic instead.

Copilot uses AI. Check for mistakes.
Comment on lines +351 to +354
<strong>Warning:</strong> Do not render these values directly in DOM,
as they are not available during SSR and will cause a hydration
mismatch. They are best suited for use as a React <code>key</code> or
in effects/callbacks.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

This warning suggests these values are “best suited for use as a React key”, but if entryId/entryKey are null during SSR and become non-null on the client, using them as keys in SSR-rendered trees can still cause hydration/reconciliation issues. Consider adjusting the guidance to emphasize use in effects/callbacks (or only in client-only renders) rather than as keys in SSR output.

Suggested change
<strong>Warning:</strong> Do not render these values directly in DOM,
as they are not available during SSR and will cause a hydration
mismatch. They are best suited for use as a React <code>key</code> or
in effects/callbacks.
<strong>Warning:</strong> Do not render these values directly into the
DOM or use them as React <code>key</code> props in trees that are
server-rendered, as they are not available during SSR and will cause
hydration mismatches. Prefer using them in effects/callbacks or in
client-only components.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +32
mismatch. Use them as a React <code>key</code> or in effects/callbacks
instead.
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

The text recommends using entryId/entryKey as a React key, but because these values are unavailable during SSR, using them as keys in SSR-rendered output can still produce hydration/reconciliation problems when they change on the client. Consider narrowing the recommendation to effects/callbacks (or only client-only rendering) instead of keys.

Suggested change
mismatch. Use them as a React <code>key</code> or in effects/callbacks
instead.
mismatch. Use them in effects or callbacks instead.

Copilot uses AI. Check for mistakes.
Covers the cases where Navigation API is unavailable (static fallback
mode) and during SSR, verifying entryId and entryKey are null.

https://claude.ai/code/session_014uqruYL8PktVwDVrSA6HJQ
@uhyo uhyo merged commit f2374a7 into master Mar 12, 2026
1 check passed
@uhyo uhyo deleted the claude/expose-navigation-api-data-tEVgT branch March 12, 2026 02:01
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.

3 participants