Skip to content

Commit be7f0c7

Browse files
committed
add example
1 parent fa652cd commit be7f0c7

7 files changed

Lines changed: 41 additions & 75 deletions

File tree

docs/router/guide/navigation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ export type LinkOptions<
133133
preloadDelay?: number
134134
// If true, will render the link without the href attribute
135135
disabled?: boolean
136-
// If true, clicking goes back via `history.back()` when the resolved target is the previous history entry (see "Direction-aware links" below)
136+
// If true, clicking goes back via `history.back()` when the resolved target is the previous history entry (see "History-aware links" below)
137137
preferBack?: boolean
138138
}
139139
```

examples/react/basic/src/main.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,14 @@ function PostComponent() {
123123
<h4 className="text-xl font-bold">{post.title}</h4>
124124
<hr className="opacity-20" />
125125
<div className="text-sm">{post.body}</div>
126+
127+
<Link
128+
to="/posts"
129+
preferBack
130+
className="py-1 px-2 rounded bg-green-600 text-white hover:opacity-90"
131+
>
132+
← Back to posts
133+
</Link>
126134
</div>
127135
)
128136
}

examples/react/basic/src/posts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export const fetchPosts = async () => {
1313
await new Promise((r) => setTimeout(r, 500))
1414
return axios
1515
.get<Array<PostType>>('https://jsonplaceholder.typicode.com/posts')
16-
.then((r) => r.data.slice(0, 10))
16+
.then((r) => r.data.slice(0, 50))
1717
}
1818

1919
export const fetchPost = async (postId: string) => {

packages/react-router/src/link.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -414,10 +414,8 @@ export function useLinkProps<
414414
return router.buildLocation(opts as any)
415415
}, [router, currentLocation, _options])
416416

417-
// History-aware links: when `preferBack` is set and the resolved target is
418-
// the previous history entry, a primary click should go back rather than push.
419-
// `true`/`'pathname'` match by pathname; `'exact'` also requires search.
420-
// Reuses the already-built `next`, so no extra `buildLocation` call.
417+
// History-aware: go back instead of pushing when the target is the previous
418+
// entry. `'exact'` also matches search. Reuses `next`, so no extra buildLocation.
421419
const isBackNavigation =
422420
!!preferBack &&
423421
resolveIsBackNavigation(
@@ -641,10 +639,8 @@ export function useLinkProps<
641639
setIsTransitioning(false)
642640
})
643641

644-
// History-aware: the target is the previous history entry, so go back
645-
// instead of pushing. This preserves forward history and the browser's
646-
// native per-entry scroll restoration. Scroll/viewTransition behavior is
647-
// handled by the router's existing popstate handling.
642+
// Target is the previous entry — pop instead of pushing to preserve forward
643+
// history and native scroll restoration (handled via the router's popstate).
648644
if (isBackNavigation) {
649645
router.history.back({ ignoreBlocker })
650646
return

packages/react-router/src/useIsBackNavigation.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@ import type {
1818
export type BackNavigationMatch = 'pathname' | 'exact'
1919

2020
/**
21-
* Decide whether navigating to `target` would land on the *previous* history
22-
* entry, meaning a `history.back()` is preferable to a push.
23-
*
24-
* Pure and framework-agnostic: it relies only on the router's in-memory
25-
* per-index entry tracking (`router.getHistoryEntry`). Returns `false` at the
26-
* start of history (index 0) and when the previous entry is unknown (e.g. a
27-
* fresh page load or deep link), so callers degrade gracefully to a normal push.
21+
* Whether navigating to `target` lands on the previous history entry (so a
22+
* `history.back()` is preferable to a push). Returns `false` at index 0 or when
23+
* the previous entry is unknown, so callers degrade gracefully to a push.
2824
*/
2925
export function resolveIsBackNavigation(
3026
router: AnyRouter,
@@ -41,22 +37,17 @@ export function resolveIsBackNavigation(
4137
}
4238

4339
/**
44-
* Returns `true` when a link/navigation to the given options would resolve to
45-
* the *previous* history entry — i.e. clicking it should go "back" rather than
46-
* push a new entry.
47-
*
48-
* This is the primitive behind the `preferBack` prop on `Link`. Use it directly
49-
* when building custom links or buttons that want the same history-aware
50-
* behavior. It returns `false` on the server and falls back to `false` whenever
51-
* the previous entry is unknown to the router, so it is always safe to branch on.
40+
* Returns `true` when navigating to the given options would resolve to the
41+
* previous history entry — i.e. it should go "back" rather than push. The
42+
* primitive behind `preferBack`, for use in custom links or buttons.
5243
*
53-
* Pass `match: 'exact'` to also require the search to match (defaults to
54-
* `'pathname'`, which restores the previous entry's exact search and scroll).
44+
* Returns `false` on the server and whenever the previous entry is unknown, so
45+
* it's always safe to branch on. Pass `match: 'exact'` to also require search to
46+
* match (defaults to `'pathname'`).
5547
*
5648
* @example
5749
* const isBack = useIsBackNavigation({ to: '/issues' })
58-
* // isBack === true -> call router.history.back()
59-
* // isBack === false -> navigate normally
50+
* // isBack ? router.history.back() : router.navigate({ to: '/issues' })
6051
*/
6152
export function useIsBackNavigation<
6253
TRouter extends AnyRouter = RegisteredRouter,

packages/router-core/src/link.ts

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -692,24 +692,9 @@ export interface LinkOptionsProps {
692692
*/
693693
preloadIntentProximity?: number
694694
/**
695-
* Makes the link history-aware: when its resolved target matches the previous
696-
* history entry, clicking it calls `history.back()` instead of pushing a new
697-
* entry. This preserves forward history and the browser's native per-entry
698-
* scroll restoration (typical for "Back to X" links).
699-
*
700-
* Controls how the target is matched against the previous entry:
701-
* - `false` (default) — disabled; behaves like a normal link.
702-
* - `true` / `'pathname'` — match by **pathname** only, so a plain
703-
* `<Link to="/x" preferBack>` pops back to the previous `/x` entry and
704-
* restores its exact search params and scroll position.
705-
* - `'exact'` — match by **pathname + search**, so the link only goes back
706-
* when its resolved search also equals the previous entry's.
707-
*
708-
* It is best-effort: the link falls back to a normal push (or replace, if `replace`
709-
* is also set) when the target does not match the previous entry, or when the
710-
* previous entry is unknown to the router (e.g. a fresh page load or deep link).
711-
* The element always renders a real `<a href>`, so keyboard navigation, "copy
712-
* link", and middle/modifier-click (open in new tab) keep working.
695+
* Makes the link history-aware: when its target is the previous history entry,
696+
* clicking goes back instead of pushing (preserving forward history + scroll).
697+
* `'exact'` also requires search to match; falls back to a normal push otherwise.
713698
* @default false
714699
*/
715700
preferBack?: boolean | 'pathname' | 'exact'

packages/router-core/src/router.ts

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -957,19 +957,17 @@ declare global {
957957
}
958958

959959
/**
960-
* A snapshot of a history entry the router has visited, keyed by its
961-
* `__TSR_index`. Used to reconstruct navigation direction (e.g. whether a
962-
* link's target is the previous entry) since the browser only exposes the
963-
* current entry.
960+
* A visited history entry keyed by its `__TSR_index`, used to resolve navigation
961+
* direction (the browser only exposes the current entry).
964962
*/
965963
export interface RouterHistoryEntry {
966964
/** The entry's `__TSR_index` in the history stack. */
967965
index: number
968-
/** The parsed (basepath-stripped) pathname, comparable to `buildLocation().pathname`. */
966+
/** Parsed (basepath-stripped) pathname, comparable to `buildLocation().pathname`. */
969967
pathname: string
970-
/** The search string including the leading `?`, comparable to `buildLocation().searchStr`. */
968+
/** Search string incl. leading `?`, comparable to `buildLocation().searchStr`. */
971969
searchStr: string
972-
/** The full href (pathname + search + hash), without origin. */
970+
/** Full href (pathname + search + hash), without origin. */
973971
href: string
974972
}
975973

@@ -1025,13 +1023,9 @@ export class RouterCore<
10251023
latestLocation!: ParsedLocation<FullSearchSchema<TRouteTree>>
10261024
pendingBuiltLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
10271025
/**
1028-
* In-memory map of visited history entries keyed by their `__TSR_index`.
1029-
*
1030-
* The browser only ever exposes the *current* history entry, so to answer
1031-
* "what was the previous entry?" (used by history-aware links via
1032-
* {@link getHistoryEntry}) the router records every location it commits,
1033-
* keyed by index. Maintained from {@link updateLatestLocation}, the single
1034-
* place `latestLocation` is recomputed from history.
1026+
* Visited history entries keyed by `__TSR_index` (recorded in
1027+
* {@link updateLatestLocation}), so the router can resolve the previous entry —
1028+
* which the browser doesn't expose. Read via {@link getHistoryEntry}.
10351029
*/
10361030
historyEntries = new Map<number, RouterHistoryEntry>()
10371031
basepath!: string
@@ -1266,14 +1260,9 @@ export class RouterCore<
12661260
this.latestLocation,
12671261
)
12681262

1269-
// Record the committed entry by its history index. `replace` keeps the same
1270-
// index, so it overwrites the slot in place; a push after going back reuses
1271-
// the truncated forward slot and overwrites it on the next call. The
1272-
// back-navigation decision only ever reads `index - 1` (always the true
1273-
// previous entry on the current linear branch), so stale higher indices
1274-
// left behind by truncation are harmless and need no pruning. We store the
1275-
// parsed (basepath-stripped) pathname so it compares directly against
1276-
// `buildLocation().pathname`.
1263+
// Record the committed entry by index. `replace` overwrites the same index;
1264+
// truncated forward entries are harmless since the back-decision only reads
1265+
// `index - 1`. Stored pathname/search compare directly with buildLocation().
12771266
const index = this.latestLocation.state.__TSR_index ?? 0
12781267
this.historyEntries.set(index, {
12791268
index,
@@ -1284,12 +1273,9 @@ export class RouterCore<
12841273
}
12851274

12861275
/**
1287-
* Look up a previously-visited history entry by its `__TSR_index`.
1288-
*
1289-
* Returns `undefined` for entries the router has not seen — e.g. entries that
1290-
* existed before the app loaded (a fresh page load or deep link). Consumers
1291-
* such as history-aware links should degrade gracefully when this is
1292-
* `undefined`.
1276+
* Look up a visited history entry by its `__TSR_index`. Returns `undefined` for
1277+
* entries the router never recorded (e.g. a fresh page load or deep link), so
1278+
* consumers should degrade gracefully.
12931279
*/
12941280
getHistoryEntry = (index: number): RouterHistoryEntry | undefined =>
12951281
this.historyEntries.get(index)

0 commit comments

Comments
 (0)