@tanstack/react-router 1.20.0 → 1.20.3-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (320) hide show
  1. package/README.md +31 -0
  2. package/dist/cjs/Asset.cjs +41 -0
  3. package/dist/cjs/Asset.cjs.map +1 -0
  4. package/dist/cjs/Asset.d.cts +2 -0
  5. package/dist/cjs/CatchBoundary.cjs +16 -12
  6. package/dist/cjs/CatchBoundary.cjs.map +1 -1
  7. package/dist/cjs/CatchBoundary.d.cts +8 -32
  8. package/dist/cjs/ClientOnly.cjs +20 -0
  9. package/dist/cjs/ClientOnly.cjs.map +1 -0
  10. package/dist/cjs/ClientOnly.d.cts +29 -0
  11. package/dist/cjs/HeadContent.cjs +155 -0
  12. package/dist/cjs/HeadContent.cjs.map +1 -0
  13. package/dist/cjs/HeadContent.d.cts +7 -0
  14. package/dist/cjs/Match.cjs +252 -0
  15. package/dist/cjs/Match.cjs.map +1 -0
  16. package/dist/cjs/Match.d.cts +8 -0
  17. package/dist/cjs/Matches.cjs +39 -287
  18. package/dist/cjs/Matches.cjs.map +1 -1
  19. package/dist/cjs/Matches.d.cts +23 -83
  20. package/dist/cjs/RouterProvider.cjs +17 -140
  21. package/dist/cjs/RouterProvider.cjs.map +1 -1
  22. package/dist/cjs/RouterProvider.d.cts +8 -27
  23. package/dist/cjs/SafeFragment.cjs +8 -0
  24. package/dist/cjs/SafeFragment.cjs.map +1 -0
  25. package/dist/cjs/SafeFragment.d.cts +1 -0
  26. package/dist/cjs/ScriptOnce.cjs +28 -0
  27. package/dist/cjs/ScriptOnce.cjs.map +1 -0
  28. package/dist/cjs/ScriptOnce.d.cts +5 -0
  29. package/dist/cjs/Scripts.cjs +51 -0
  30. package/dist/cjs/Scripts.cjs.map +1 -0
  31. package/dist/cjs/Scripts.d.cts +1 -0
  32. package/dist/cjs/ScrollRestoration.cjs +39 -0
  33. package/dist/cjs/ScrollRestoration.cjs.map +1 -0
  34. package/dist/cjs/ScrollRestoration.d.cts +14 -0
  35. package/dist/cjs/Transitioner.cjs +115 -0
  36. package/dist/cjs/Transitioner.cjs.map +1 -0
  37. package/dist/cjs/Transitioner.d.cts +1 -0
  38. package/dist/cjs/awaited.cjs +12 -65
  39. package/dist/cjs/awaited.cjs.map +1 -1
  40. package/dist/cjs/awaited.d.cts +4 -4
  41. package/dist/cjs/fileRoute.cjs +41 -15
  42. package/dist/cjs/fileRoute.cjs.map +1 -1
  43. package/dist/cjs/fileRoute.d.cts +33 -108
  44. package/dist/cjs/history.d.cts +1 -0
  45. package/dist/cjs/index.cjs +216 -73
  46. package/dist/cjs/index.cjs.map +1 -1
  47. package/dist/cjs/index.d.cts +52 -29
  48. package/dist/cjs/lazyRouteComponent.cjs +40 -29
  49. package/dist/cjs/lazyRouteComponent.cjs.map +1 -1
  50. package/dist/cjs/lazyRouteComponent.d.cts +1 -1
  51. package/dist/cjs/link.cjs +212 -106
  52. package/dist/cjs/link.cjs.map +1 -1
  53. package/dist/cjs/link.d.cts +41 -86
  54. package/dist/cjs/matchContext.cjs +27 -0
  55. package/dist/cjs/matchContext.cjs.map +1 -0
  56. package/dist/cjs/matchContext.d.cts +3 -0
  57. package/dist/cjs/not-found.cjs +9 -15
  58. package/dist/cjs/not-found.cjs.map +1 -1
  59. package/dist/cjs/not-found.d.cts +5 -22
  60. package/dist/cjs/renderRouteNotFound.cjs +22 -0
  61. package/dist/cjs/renderRouteNotFound.cjs.map +1 -0
  62. package/dist/cjs/renderRouteNotFound.d.cts +2 -0
  63. package/dist/cjs/route.cjs +110 -79
  64. package/dist/cjs/route.cjs.map +1 -1
  65. package/dist/cjs/route.d.cts +64 -361
  66. package/dist/cjs/router.cjs +12 -1237
  67. package/dist/cjs/router.cjs.map +1 -1
  68. package/dist/cjs/router.d.cts +69 -237
  69. package/dist/cjs/routerContext.cjs +1 -1
  70. package/dist/cjs/routerContext.cjs.map +1 -1
  71. package/dist/cjs/routerContext.d.cts +7 -2
  72. package/dist/cjs/scroll-restoration.cjs +16 -177
  73. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  74. package/dist/cjs/scroll-restoration.d.cts +1 -18
  75. package/dist/cjs/serializer.d.cts +6 -0
  76. package/dist/cjs/structuralSharing.d.cts +8 -0
  77. package/dist/cjs/typePrimitives.d.cts +16 -0
  78. package/dist/cjs/useBlocker.cjs +138 -9
  79. package/dist/cjs/useBlocker.cjs.map +1 -1
  80. package/dist/cjs/useBlocker.d.cts +64 -7
  81. package/dist/cjs/useCanGoBack.cjs +8 -0
  82. package/dist/cjs/useCanGoBack.cjs.map +1 -0
  83. package/dist/cjs/useCanGoBack.d.cts +1 -0
  84. package/dist/cjs/useLoaderData.cjs +15 -0
  85. package/dist/cjs/useLoaderData.cjs.map +1 -0
  86. package/dist/cjs/useLoaderData.d.cts +8 -0
  87. package/dist/cjs/useLoaderDeps.cjs +14 -0
  88. package/dist/cjs/useLoaderDeps.cjs.map +1 -0
  89. package/dist/cjs/useLoaderDeps.d.cts +8 -0
  90. package/dist/cjs/useLocation.cjs +10 -0
  91. package/dist/cjs/useLocation.cjs.map +1 -0
  92. package/dist/cjs/useLocation.d.cts +7 -0
  93. package/dist/cjs/useMatch.cjs +47 -0
  94. package/dist/cjs/useMatch.cjs.map +1 -0
  95. package/dist/cjs/useMatch.d.cts +10 -0
  96. package/dist/cjs/useNavigate.cjs +18 -19
  97. package/dist/cjs/useNavigate.cjs.map +1 -1
  98. package/dist/cjs/useNavigate.d.cts +4 -8
  99. package/dist/cjs/useParams.cjs +8 -8
  100. package/dist/cjs/useParams.cjs.map +1 -1
  101. package/dist/cjs/useParams.d.cts +9 -8
  102. package/dist/cjs/useRouteContext.cjs +3 -3
  103. package/dist/cjs/useRouteContext.cjs.map +1 -1
  104. package/dist/cjs/useRouteContext.d.cts +3 -7
  105. package/dist/cjs/useRouter.cjs.map +1 -1
  106. package/dist/cjs/useRouter.d.cts +3 -4
  107. package/dist/cjs/useRouterState.cjs +18 -1
  108. package/dist/cjs/useRouterState.cjs.map +1 -1
  109. package/dist/cjs/useRouterState.d.cts +8 -6
  110. package/dist/cjs/useSearch.cjs +7 -4
  111. package/dist/cjs/useSearch.cjs.map +1 -1
  112. package/dist/cjs/useSearch.d.cts +9 -7
  113. package/dist/cjs/utils.cjs +40 -122
  114. package/dist/cjs/utils.cjs.map +1 -1
  115. package/dist/cjs/utils.d.cts +46 -50
  116. package/dist/esm/Asset.d.ts +2 -0
  117. package/dist/esm/Asset.js +41 -0
  118. package/dist/esm/Asset.js.map +1 -0
  119. package/dist/esm/CatchBoundary.d.ts +8 -32
  120. package/dist/esm/CatchBoundary.js +16 -12
  121. package/dist/esm/CatchBoundary.js.map +1 -1
  122. package/dist/esm/ClientOnly.d.ts +29 -0
  123. package/dist/esm/ClientOnly.js +20 -0
  124. package/dist/esm/ClientOnly.js.map +1 -0
  125. package/dist/esm/HeadContent.d.ts +7 -0
  126. package/dist/esm/HeadContent.js +139 -0
  127. package/dist/esm/HeadContent.js.map +1 -0
  128. package/dist/esm/Match.d.ts +8 -0
  129. package/dist/esm/Match.js +235 -0
  130. package/dist/esm/Match.js.map +1 -0
  131. package/dist/esm/Matches.d.ts +23 -83
  132. package/dist/esm/Matches.js +36 -284
  133. package/dist/esm/Matches.js.map +1 -1
  134. package/dist/esm/RouterProvider.d.ts +8 -27
  135. package/dist/esm/RouterProvider.js +20 -126
  136. package/dist/esm/RouterProvider.js.map +1 -1
  137. package/dist/esm/SafeFragment.d.ts +1 -0
  138. package/dist/esm/SafeFragment.js +8 -0
  139. package/dist/esm/SafeFragment.js.map +1 -0
  140. package/dist/esm/ScriptOnce.d.ts +5 -0
  141. package/dist/esm/ScriptOnce.js +28 -0
  142. package/dist/esm/ScriptOnce.js.map +1 -0
  143. package/dist/esm/Scripts.d.ts +1 -0
  144. package/dist/esm/Scripts.js +51 -0
  145. package/dist/esm/Scripts.js.map +1 -0
  146. package/dist/esm/ScrollRestoration.d.ts +14 -0
  147. package/dist/esm/ScrollRestoration.js +39 -0
  148. package/dist/esm/ScrollRestoration.js.map +1 -0
  149. package/dist/esm/Transitioner.d.ts +1 -0
  150. package/dist/esm/Transitioner.js +98 -0
  151. package/dist/esm/Transitioner.js.map +1 -0
  152. package/dist/esm/awaited.d.ts +4 -4
  153. package/dist/esm/awaited.js +12 -65
  154. package/dist/esm/awaited.js.map +1 -1
  155. package/dist/esm/fileRoute.d.ts +33 -108
  156. package/dist/esm/fileRoute.js +38 -12
  157. package/dist/esm/fileRoute.js.map +1 -1
  158. package/dist/esm/history.d.ts +1 -0
  159. package/dist/esm/index.d.ts +52 -29
  160. package/dist/esm/index.js +41 -29
  161. package/dist/esm/index.js.map +1 -1
  162. package/dist/esm/lazyRouteComponent.d.ts +1 -1
  163. package/dist/esm/lazyRouteComponent.js +40 -29
  164. package/dist/esm/lazyRouteComponent.js.map +1 -1
  165. package/dist/esm/link.d.ts +41 -86
  166. package/dist/esm/link.js +212 -106
  167. package/dist/esm/link.js.map +1 -1
  168. package/dist/esm/matchContext.d.ts +3 -0
  169. package/dist/esm/matchContext.js +10 -0
  170. package/dist/esm/matchContext.js.map +1 -0
  171. package/dist/esm/not-found.d.ts +5 -22
  172. package/dist/esm/not-found.js +9 -15
  173. package/dist/esm/not-found.js.map +1 -1
  174. package/dist/esm/renderRouteNotFound.d.ts +2 -0
  175. package/dist/esm/renderRouteNotFound.js +22 -0
  176. package/dist/esm/renderRouteNotFound.js.map +1 -0
  177. package/dist/esm/route.d.ts +64 -361
  178. package/dist/esm/route.js +103 -72
  179. package/dist/esm/route.js.map +1 -1
  180. package/dist/esm/router.d.ts +69 -237
  181. package/dist/esm/router.js +13 -1238
  182. package/dist/esm/router.js.map +1 -1
  183. package/dist/esm/routerContext.d.ts +7 -2
  184. package/dist/esm/routerContext.js +1 -1
  185. package/dist/esm/routerContext.js.map +1 -1
  186. package/dist/esm/scroll-restoration.d.ts +1 -18
  187. package/dist/esm/scroll-restoration.js +17 -161
  188. package/dist/esm/scroll-restoration.js.map +1 -1
  189. package/dist/esm/serializer.d.ts +6 -0
  190. package/dist/esm/structuralSharing.d.ts +8 -0
  191. package/dist/esm/typePrimitives.d.ts +16 -0
  192. package/dist/esm/useBlocker.d.ts +64 -7
  193. package/dist/esm/useBlocker.js +138 -9
  194. package/dist/esm/useBlocker.js.map +1 -1
  195. package/dist/esm/useCanGoBack.d.ts +1 -0
  196. package/dist/esm/useCanGoBack.js +8 -0
  197. package/dist/esm/useCanGoBack.js.map +1 -0
  198. package/dist/esm/useLoaderData.d.ts +8 -0
  199. package/dist/esm/useLoaderData.js +15 -0
  200. package/dist/esm/useLoaderData.js.map +1 -0
  201. package/dist/esm/useLoaderDeps.d.ts +8 -0
  202. package/dist/esm/useLoaderDeps.js +14 -0
  203. package/dist/esm/useLoaderDeps.js.map +1 -0
  204. package/dist/esm/useLocation.d.ts +7 -0
  205. package/dist/esm/useLocation.js +10 -0
  206. package/dist/esm/useLocation.js.map +1 -0
  207. package/dist/esm/useMatch.d.ts +10 -0
  208. package/dist/esm/useMatch.js +30 -0
  209. package/dist/esm/useMatch.js.map +1 -0
  210. package/dist/esm/useNavigate.d.ts +4 -8
  211. package/dist/esm/useNavigate.js +18 -19
  212. package/dist/esm/useNavigate.js.map +1 -1
  213. package/dist/esm/useParams.d.ts +9 -8
  214. package/dist/esm/useParams.js +8 -8
  215. package/dist/esm/useParams.js.map +1 -1
  216. package/dist/esm/useRouteContext.d.ts +3 -7
  217. package/dist/esm/useRouteContext.js +2 -2
  218. package/dist/esm/useRouteContext.js.map +1 -1
  219. package/dist/esm/useRouter.d.ts +3 -4
  220. package/dist/esm/useRouter.js.map +1 -1
  221. package/dist/esm/useRouterState.d.ts +8 -6
  222. package/dist/esm/useRouterState.js +18 -1
  223. package/dist/esm/useRouterState.js.map +1 -1
  224. package/dist/esm/useSearch.d.ts +9 -7
  225. package/dist/esm/useSearch.js +6 -3
  226. package/dist/esm/useSearch.js.map +1 -1
  227. package/dist/esm/utils.d.ts +46 -50
  228. package/dist/esm/utils.js +41 -123
  229. package/dist/esm/utils.js.map +1 -1
  230. package/package.json +30 -31
  231. package/src/Asset.tsx +40 -0
  232. package/src/CatchBoundary.tsx +35 -19
  233. package/src/ClientOnly.tsx +68 -0
  234. package/src/HeadContent.tsx +174 -0
  235. package/src/Match.tsx +330 -0
  236. package/src/Matches.tsx +149 -558
  237. package/src/RouterProvider.tsx +58 -212
  238. package/src/SafeFragment.tsx +5 -0
  239. package/src/ScriptOnce.tsx +32 -0
  240. package/src/Scripts.tsx +65 -0
  241. package/src/ScrollRestoration.tsx +69 -0
  242. package/src/Transitioner.tsx +130 -0
  243. package/src/awaited.tsx +16 -87
  244. package/src/fileRoute.ts +145 -248
  245. package/src/history.ts +2 -1
  246. package/src/index.tsx +368 -30
  247. package/src/lazyRouteComponent.tsx +68 -54
  248. package/src/link.tsx +397 -522
  249. package/src/matchContext.tsx +8 -0
  250. package/src/not-found.tsx +13 -34
  251. package/src/renderRouteNotFound.tsx +27 -0
  252. package/src/route.tsx +572 -0
  253. package/src/router.ts +99 -2067
  254. package/src/routerContext.tsx +8 -2
  255. package/src/scroll-restoration.tsx +23 -224
  256. package/src/serializer.ts +7 -0
  257. package/src/structuralSharing.ts +47 -0
  258. package/src/typePrimitives.ts +84 -0
  259. package/src/useBlocker.tsx +297 -15
  260. package/src/useCanGoBack.ts +5 -0
  261. package/src/useLoaderData.tsx +80 -0
  262. package/src/useLoaderDeps.tsx +58 -0
  263. package/src/useLocation.tsx +41 -0
  264. package/src/useMatch.tsx +119 -0
  265. package/src/useNavigate.tsx +41 -61
  266. package/src/useParams.tsx +88 -23
  267. package/src/useRouteContext.ts +24 -18
  268. package/src/useRouter.tsx +4 -5
  269. package/src/useRouterState.tsx +52 -10
  270. package/src/useSearch.tsx +87 -24
  271. package/src/utils.ts +97 -312
  272. package/dist/cjs/createServerFn.cjs +0 -40
  273. package/dist/cjs/createServerFn.cjs.map +0 -1
  274. package/dist/cjs/createServerFn.d.cts +0 -44
  275. package/dist/cjs/defer.cjs +0 -30
  276. package/dist/cjs/defer.cjs.map +0 -1
  277. package/dist/cjs/defer.d.cts +0 -25
  278. package/dist/cjs/location.d.cts +0 -12
  279. package/dist/cjs/path.cjs +0 -213
  280. package/dist/cjs/path.cjs.map +0 -1
  281. package/dist/cjs/path.d.cts +0 -24
  282. package/dist/cjs/qss.cjs +0 -45
  283. package/dist/cjs/qss.cjs.map +0 -1
  284. package/dist/cjs/qss.d.cts +0 -2
  285. package/dist/cjs/redirects.cjs +0 -16
  286. package/dist/cjs/redirects.cjs.map +0 -1
  287. package/dist/cjs/redirects.d.cts +0 -18
  288. package/dist/cjs/routeInfo.d.cts +0 -33
  289. package/dist/cjs/searchParams.cjs +0 -63
  290. package/dist/cjs/searchParams.cjs.map +0 -1
  291. package/dist/cjs/searchParams.d.cts +0 -7
  292. package/dist/esm/createServerFn.d.ts +0 -44
  293. package/dist/esm/createServerFn.js +0 -40
  294. package/dist/esm/createServerFn.js.map +0 -1
  295. package/dist/esm/defer.d.ts +0 -25
  296. package/dist/esm/defer.js +0 -30
  297. package/dist/esm/defer.js.map +0 -1
  298. package/dist/esm/location.d.ts +0 -12
  299. package/dist/esm/path.d.ts +0 -24
  300. package/dist/esm/path.js +0 -213
  301. package/dist/esm/path.js.map +0 -1
  302. package/dist/esm/qss.d.ts +0 -2
  303. package/dist/esm/qss.js +0 -45
  304. package/dist/esm/qss.js.map +0 -1
  305. package/dist/esm/redirects.d.ts +0 -18
  306. package/dist/esm/redirects.js +0 -16
  307. package/dist/esm/redirects.js.map +0 -1
  308. package/dist/esm/routeInfo.d.ts +0 -33
  309. package/dist/esm/searchParams.d.ts +0 -7
  310. package/dist/esm/searchParams.js +0 -63
  311. package/dist/esm/searchParams.js.map +0 -1
  312. package/src/createServerFn.ts +0 -107
  313. package/src/defer.ts +0 -70
  314. package/src/location.ts +0 -13
  315. package/src/path.ts +0 -280
  316. package/src/qss.ts +0 -53
  317. package/src/redirects.ts +0 -56
  318. package/src/route.ts +0 -1356
  319. package/src/routeInfo.ts +0 -62
  320. package/src/searchParams.ts +0 -79
@@ -0,0 +1,68 @@
1
+ import React from 'react'
2
+
3
+ export interface ClientOnlyProps {
4
+ /**
5
+ * The children to render if the JS is loaded.
6
+ */
7
+ children: React.ReactNode
8
+ /**
9
+ * The fallback component to render if the JS is not yet loaded.
10
+ */
11
+ fallback?: React.ReactNode
12
+ }
13
+
14
+ /**
15
+ * Render the children only after the JS has loaded client-side. Use an optional
16
+ * fallback component if the JS is not yet loaded.
17
+ *
18
+ * @example
19
+ * Render a Chart component if JS loads, renders a simple FakeChart
20
+ * component server-side or if there is no JS. The FakeChart can have only the
21
+ * UI without the behavior or be a loading spinner or skeleton.
22
+ *
23
+ * ```tsx
24
+ * return (
25
+ * <ClientOnly fallback={<FakeChart />}>
26
+ * <Chart />
27
+ * </ClientOnly>
28
+ * )
29
+ * ```
30
+ */
31
+ export function ClientOnly({ children, fallback = null }: ClientOnlyProps) {
32
+ return useHydrated() ? (
33
+ <React.Fragment>{children}</React.Fragment>
34
+ ) : (
35
+ <React.Fragment>{fallback}</React.Fragment>
36
+ )
37
+ }
38
+
39
+ /**
40
+ * Return a boolean indicating if the JS has been hydrated already.
41
+ * When doing Server-Side Rendering, the result will always be false.
42
+ * When doing Client-Side Rendering, the result will always be false on the
43
+ * first render and true from then on. Even if a new component renders it will
44
+ * always start with true.
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * // Disable a button that needs JS to work.
49
+ * let hydrated = useHydrated()
50
+ * return (
51
+ * <button type="button" disabled={!hydrated} onClick={doSomethingCustom}>
52
+ * Click me
53
+ * </button>
54
+ * )
55
+ * ```
56
+ * @returns True if the JS has been hydrated already, false otherwise.
57
+ */
58
+ function useHydrated(): boolean {
59
+ return React.useSyncExternalStore(
60
+ subscribe,
61
+ () => true,
62
+ () => false,
63
+ )
64
+ }
65
+
66
+ function subscribe() {
67
+ return () => {}
68
+ }
@@ -0,0 +1,174 @@
1
+ import * as React from 'react'
2
+ import { Asset } from './Asset'
3
+ import { useRouter } from './useRouter'
4
+ import { useRouterState } from './useRouterState'
5
+ import type { RouterManagedTag } from '@tanstack/router-core'
6
+
7
+ export const useTags = () => {
8
+ const router = useRouter()
9
+
10
+ const routeMeta = useRouterState({
11
+ select: (state) => {
12
+ return state.matches.map((match) => match.meta!).filter(Boolean)
13
+ },
14
+ })
15
+
16
+ const meta: Array<RouterManagedTag> = React.useMemo(() => {
17
+ const resultMeta: Array<RouterManagedTag> = []
18
+ const metaByAttribute: Record<string, true> = {}
19
+ let title: RouterManagedTag | undefined
20
+ ;[...routeMeta].reverse().forEach((metas) => {
21
+ ;[...metas].reverse().forEach((m) => {
22
+ if (!m) return
23
+
24
+ if (m.title) {
25
+ if (!title) {
26
+ title = {
27
+ tag: 'title',
28
+ children: m.title,
29
+ }
30
+ }
31
+ } else {
32
+ const attribute = m.name ?? m.property
33
+ if (attribute) {
34
+ if (metaByAttribute[attribute]) {
35
+ return
36
+ } else {
37
+ metaByAttribute[attribute] = true
38
+ }
39
+ }
40
+
41
+ resultMeta.push({
42
+ tag: 'meta',
43
+ attrs: {
44
+ ...m,
45
+ },
46
+ })
47
+ }
48
+ })
49
+ })
50
+
51
+ if (title) {
52
+ resultMeta.push(title)
53
+ }
54
+
55
+ resultMeta.reverse()
56
+
57
+ return resultMeta
58
+ }, [routeMeta])
59
+
60
+ const links = useRouterState({
61
+ select: (state) => {
62
+ const constructed = state.matches
63
+ .map((match) => match.links!)
64
+ .filter(Boolean)
65
+ .flat(1)
66
+ .map((link) => ({
67
+ tag: 'link',
68
+ attrs: {
69
+ ...link,
70
+ },
71
+ })) satisfies Array<RouterManagedTag>
72
+
73
+ const manifest = router.ssr?.manifest
74
+
75
+ // These are the assets extracted from the ViteManifest
76
+ // using the `startManifestPlugin`
77
+ const assets = state.matches
78
+ .map((match) => manifest?.routes[match.routeId]?.assets ?? [])
79
+ .filter(Boolean)
80
+ .flat(1)
81
+ .filter((asset) => asset.tag === 'link')
82
+ .map(
83
+ (asset) =>
84
+ ({
85
+ tag: 'link',
86
+ attrs: {
87
+ ...asset.attrs,
88
+ suppressHydrationWarning: true,
89
+ },
90
+ }) satisfies RouterManagedTag,
91
+ )
92
+
93
+ return [...constructed, ...assets]
94
+ },
95
+ structuralSharing: true as any,
96
+ })
97
+
98
+ const preloadMeta = useRouterState({
99
+ select: (state) => {
100
+ const preloadMeta: Array<RouterManagedTag> = []
101
+
102
+ state.matches
103
+ .map((match) => router.looseRoutesById[match.routeId]!)
104
+ .forEach((route) =>
105
+ router.ssr?.manifest?.routes[route.id]?.preloads
106
+ ?.filter(Boolean)
107
+ .forEach((preload) => {
108
+ preloadMeta.push({
109
+ tag: 'link',
110
+ attrs: {
111
+ rel: 'modulepreload',
112
+ href: preload,
113
+ },
114
+ })
115
+ }),
116
+ )
117
+
118
+ return preloadMeta
119
+ },
120
+ structuralSharing: true as any,
121
+ })
122
+
123
+ const headScripts = useRouterState({
124
+ select: (state) =>
125
+ (
126
+ state.matches
127
+ .map((match) => match.headScripts!)
128
+ .flat(1)
129
+ .filter(Boolean) as Array<RouterManagedTag>
130
+ ).map(({ children, ...script }) => ({
131
+ tag: 'script',
132
+ attrs: {
133
+ ...script,
134
+ },
135
+ children,
136
+ })),
137
+ structuralSharing: true as any,
138
+ })
139
+
140
+ return uniqBy(
141
+ [
142
+ ...meta,
143
+ ...preloadMeta,
144
+ ...links,
145
+ ...headScripts,
146
+ ] as Array<RouterManagedTag>,
147
+ (d) => {
148
+ return JSON.stringify(d)
149
+ },
150
+ )
151
+ }
152
+
153
+ /**
154
+ * @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route.
155
+ * It should be rendered in the `<head>` of your document.
156
+ */
157
+ export function HeadContent() {
158
+ const tags = useTags()
159
+ return tags.map((tag) => (
160
+ <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} />
161
+ ))
162
+ }
163
+
164
+ function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
165
+ const seen = new Set<string>()
166
+ return arr.filter((item) => {
167
+ const key = fn(item)
168
+ if (seen.has(key)) {
169
+ return false
170
+ }
171
+ seen.add(key)
172
+ return true
173
+ })
174
+ }
package/src/Match.tsx ADDED
@@ -0,0 +1,330 @@
1
+ import * as React from 'react'
2
+ import invariant from 'tiny-invariant'
3
+ import warning from 'tiny-warning'
4
+ import {
5
+ createControlledPromise,
6
+ getLocationChangeInfo,
7
+ isNotFound,
8
+ isRedirect,
9
+ pick,
10
+ rootRouteId,
11
+ } from '@tanstack/router-core'
12
+ import { CatchBoundary, ErrorComponent } from './CatchBoundary'
13
+ import { useRouterState } from './useRouterState'
14
+ import { useRouter } from './useRouter'
15
+ import { CatchNotFound } from './not-found'
16
+ import { matchContext } from './matchContext'
17
+ import { SafeFragment } from './SafeFragment'
18
+ import { renderRouteNotFound } from './renderRouteNotFound'
19
+ import { ScrollRestoration } from './scroll-restoration'
20
+ import type { AnyRoute, ParsedLocation } from '@tanstack/router-core'
21
+
22
+ export const Match = React.memo(function MatchImpl({
23
+ matchId,
24
+ }: {
25
+ matchId: string
26
+ }) {
27
+ const router = useRouter()
28
+ const routeId = useRouterState({
29
+ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
30
+ })
31
+
32
+ invariant(
33
+ routeId,
34
+ `Could not find routeId for matchId "${matchId}". Please file an issue!`,
35
+ )
36
+
37
+ const route: AnyRoute = router.routesById[routeId]
38
+
39
+ const PendingComponent =
40
+ route.options.pendingComponent ?? router.options.defaultPendingComponent
41
+
42
+ const pendingElement = PendingComponent ? <PendingComponent /> : null
43
+
44
+ const routeErrorComponent =
45
+ route.options.errorComponent ?? router.options.defaultErrorComponent
46
+
47
+ const routeOnCatch = route.options.onCatch ?? router.options.defaultOnCatch
48
+
49
+ const routeNotFoundComponent = route.isRoot
50
+ ? // If it's the root route, use the globalNotFound option, with fallback to the notFoundRoute's component
51
+ (route.options.notFoundComponent ??
52
+ router.options.notFoundRoute?.options.component)
53
+ : route.options.notFoundComponent
54
+
55
+ const ResolvedSuspenseBoundary =
56
+ // If we're on the root route, allow forcefully wrapping in suspense
57
+ (!route.isRoot || route.options.wrapInSuspense) &&
58
+ (route.options.wrapInSuspense ??
59
+ PendingComponent ??
60
+ (route.options.errorComponent as any)?.preload)
61
+ ? React.Suspense
62
+ : SafeFragment
63
+
64
+ const ResolvedCatchBoundary = routeErrorComponent
65
+ ? CatchBoundary
66
+ : SafeFragment
67
+
68
+ const ResolvedNotFoundBoundary = routeNotFoundComponent
69
+ ? CatchNotFound
70
+ : SafeFragment
71
+
72
+ const resetKey = useRouterState({
73
+ select: (s) => s.loadedAt,
74
+ })
75
+
76
+ const parentRouteId = useRouterState({
77
+ select: (s) => {
78
+ const index = s.matches.findIndex((d) => d.id === matchId)
79
+ return s.matches[index - 1]?.routeId as string
80
+ },
81
+ })
82
+
83
+ return (
84
+ <>
85
+ <matchContext.Provider value={matchId}>
86
+ <ResolvedSuspenseBoundary fallback={pendingElement}>
87
+ <ResolvedCatchBoundary
88
+ getResetKey={() => resetKey}
89
+ errorComponent={routeErrorComponent || ErrorComponent}
90
+ onCatch={(error, errorInfo) => {
91
+ // Forward not found errors (we don't want to show the error component for these)
92
+ if (isNotFound(error)) throw error
93
+ warning(false, `Error in route match: ${matchId}`)
94
+ routeOnCatch?.(error, errorInfo)
95
+ }}
96
+ >
97
+ <ResolvedNotFoundBoundary
98
+ fallback={(error) => {
99
+ // If the current not found handler doesn't exist or it has a
100
+ // route ID which doesn't match the current route, rethrow the error
101
+ if (
102
+ !routeNotFoundComponent ||
103
+ (error.routeId && error.routeId !== routeId) ||
104
+ (!error.routeId && !route.isRoot)
105
+ )
106
+ throw error
107
+
108
+ return React.createElement(routeNotFoundComponent, error as any)
109
+ }}
110
+ >
111
+ <MatchInner matchId={matchId} />
112
+ </ResolvedNotFoundBoundary>
113
+ </ResolvedCatchBoundary>
114
+ </ResolvedSuspenseBoundary>
115
+ </matchContext.Provider>
116
+ {parentRouteId === rootRouteId && router.options.scrollRestoration ? (
117
+ <>
118
+ <OnRendered />
119
+ <ScrollRestoration />
120
+ </>
121
+ ) : null}
122
+ </>
123
+ )
124
+ })
125
+
126
+ // On Rendered can't happen above the root layout because it actually
127
+ // renders a dummy dom element to track the rendered state of the app.
128
+ // We render a script tag with a key that changes based on the current
129
+ // location state.key. Also, because it's below the root layout, it
130
+ // allows us to fire onRendered events even after a hydration mismatch
131
+ // error that occurred above the root layout (like bad head/link tags,
132
+ // which is common).
133
+ function OnRendered() {
134
+ const router = useRouter()
135
+
136
+ const prevLocationRef = React.useRef<undefined | ParsedLocation<{}>>(
137
+ undefined,
138
+ )
139
+
140
+ return (
141
+ <script
142
+ key={router.latestLocation.state.key}
143
+ suppressHydrationWarning
144
+ ref={(el) => {
145
+ if (
146
+ el &&
147
+ (prevLocationRef.current === undefined ||
148
+ prevLocationRef.current.href !== router.latestLocation.href)
149
+ ) {
150
+ router.emit({
151
+ type: 'onRendered',
152
+ ...getLocationChangeInfo(router.state),
153
+ })
154
+ prevLocationRef.current = router.latestLocation
155
+ }
156
+ }}
157
+ />
158
+ )
159
+ }
160
+
161
+ export const MatchInner = React.memo(function MatchInnerImpl({
162
+ matchId,
163
+ }: {
164
+ matchId: string
165
+ }): any {
166
+ const router = useRouter()
167
+
168
+ const { match, key, routeId } = useRouterState({
169
+ select: (s) => {
170
+ const matchIndex = s.matches.findIndex((d) => d.id === matchId)
171
+ const match = s.matches[matchIndex]!
172
+ const routeId = match.routeId as string
173
+
174
+ const remountFn =
175
+ (router.routesById[routeId] as AnyRoute).options.remountDeps ??
176
+ router.options.defaultRemountDeps
177
+ const remountDeps = remountFn?.({
178
+ routeId,
179
+ loaderDeps: match.loaderDeps,
180
+ params: match._strictParams,
181
+ search: match._strictSearch,
182
+ })
183
+ const key = remountDeps ? JSON.stringify(remountDeps) : undefined
184
+
185
+ return {
186
+ key,
187
+ routeId,
188
+ match: pick(match, ['id', 'status', 'error']),
189
+ }
190
+ },
191
+ structuralSharing: true as any,
192
+ })
193
+
194
+ const route = router.routesById[routeId] as AnyRoute
195
+
196
+ const out = React.useMemo(() => {
197
+ const Comp = route.options.component ?? router.options.defaultComponent
198
+ if (Comp) {
199
+ return <Comp key={key} />
200
+ }
201
+ return <Outlet />
202
+ }, [key, route.options.component, router.options.defaultComponent])
203
+
204
+ const RouteErrorComponent =
205
+ (route.options.errorComponent ?? router.options.defaultErrorComponent) ||
206
+ ErrorComponent
207
+
208
+ if (match.status === 'notFound') {
209
+ invariant(isNotFound(match.error), 'Expected a notFound error')
210
+ return renderRouteNotFound(router, route, match.error)
211
+ }
212
+
213
+ if (match.status === 'redirected') {
214
+ // Redirects should be handled by the router transition. If we happen to
215
+ // encounter a redirect here, it's a bug. Let's warn, but render nothing.
216
+ invariant(isRedirect(match.error), 'Expected a redirect error')
217
+
218
+ // warning(
219
+ // false,
220
+ // 'Tried to render a redirected route match! This is a weird circumstance, please file an issue!',
221
+ // )
222
+ throw router.getMatch(match.id)?.loadPromise
223
+ }
224
+
225
+ if (match.status === 'error') {
226
+ // If we're on the server, we need to use React's new and super
227
+ // wonky api for throwing errors from a server side render inside
228
+ // of a suspense boundary. This is the only way to get
229
+ // renderToPipeableStream to not hang indefinitely.
230
+ // We'll serialize the error and rethrow it on the client.
231
+ if (router.isServer) {
232
+ return (
233
+ <RouteErrorComponent
234
+ error={match.error as any}
235
+ reset={undefined as any}
236
+ info={{
237
+ componentStack: '',
238
+ }}
239
+ />
240
+ )
241
+ }
242
+
243
+ throw match.error
244
+ }
245
+
246
+ if (match.status === 'pending') {
247
+ // We're pending, and if we have a minPendingMs, we need to wait for it
248
+ const pendingMinMs =
249
+ route.options.pendingMinMs ?? router.options.defaultPendingMinMs
250
+
251
+ if (pendingMinMs && !router.getMatch(match.id)?.minPendingPromise) {
252
+ // Create a promise that will resolve after the minPendingMs
253
+ if (!router.isServer) {
254
+ const minPendingPromise = createControlledPromise<void>()
255
+
256
+ Promise.resolve().then(() => {
257
+ router.updateMatch(match.id, (prev) => ({
258
+ ...prev,
259
+ minPendingPromise,
260
+ }))
261
+ })
262
+
263
+ setTimeout(() => {
264
+ minPendingPromise.resolve()
265
+
266
+ // We've handled the minPendingPromise, so we can delete it
267
+ router.updateMatch(match.id, (prev) => ({
268
+ ...prev,
269
+ minPendingPromise: undefined,
270
+ }))
271
+ }, pendingMinMs)
272
+ }
273
+ }
274
+ throw router.getMatch(match.id)?.loadPromise
275
+ }
276
+
277
+ return out
278
+ })
279
+
280
+ export const Outlet = React.memo(function OutletImpl() {
281
+ const router = useRouter()
282
+ const matchId = React.useContext(matchContext)
283
+ const routeId = useRouterState({
284
+ select: (s) => s.matches.find((d) => d.id === matchId)?.routeId as string,
285
+ })
286
+
287
+ const route = router.routesById[routeId]!
288
+
289
+ const parentGlobalNotFound = useRouterState({
290
+ select: (s) => {
291
+ const matches = s.matches
292
+ const parentMatch = matches.find((d) => d.id === matchId)
293
+ invariant(
294
+ parentMatch,
295
+ `Could not find parent match for matchId "${matchId}"`,
296
+ )
297
+ return parentMatch.globalNotFound
298
+ },
299
+ })
300
+
301
+ const childMatchId = useRouterState({
302
+ select: (s) => {
303
+ const matches = s.matches
304
+ const index = matches.findIndex((d) => d.id === matchId)
305
+ return matches[index + 1]?.id
306
+ },
307
+ })
308
+
309
+ if (parentGlobalNotFound) {
310
+ return renderRouteNotFound(router, route, undefined)
311
+ }
312
+
313
+ if (!childMatchId) {
314
+ return null
315
+ }
316
+
317
+ const nextMatch = <Match matchId={childMatchId} />
318
+
319
+ const pendingElement = router.options.defaultPendingComponent ? (
320
+ <router.options.defaultPendingComponent />
321
+ ) : null
322
+
323
+ if (matchId === rootRouteId) {
324
+ return (
325
+ <React.Suspense fallback={pendingElement}>{nextMatch}</React.Suspense>
326
+ )
327
+ }
328
+
329
+ return nextMatch
330
+ })