@tanstack/react-router 1.104.1 → 1.105.5

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 (49) hide show
  1. package/dist/cjs/Asset.cjs +41 -0
  2. package/dist/cjs/Asset.cjs.map +1 -0
  3. package/dist/cjs/Asset.d.cts +2 -0
  4. package/dist/cjs/HeadContent.cjs +138 -0
  5. package/dist/cjs/HeadContent.cjs.map +1 -0
  6. package/dist/cjs/HeadContent.d.cts +7 -0
  7. package/dist/cjs/Matches.cjs.map +1 -1
  8. package/dist/cjs/Matches.d.cts +1 -0
  9. package/dist/cjs/Scripts.cjs +50 -0
  10. package/dist/cjs/Scripts.cjs.map +1 -0
  11. package/dist/cjs/Scripts.d.cts +1 -0
  12. package/dist/cjs/index.cjs +6 -0
  13. package/dist/cjs/index.cjs.map +1 -1
  14. package/dist/cjs/index.d.cts +3 -0
  15. package/dist/cjs/route.cjs.map +1 -1
  16. package/dist/cjs/route.d.cts +10 -7
  17. package/dist/cjs/router.cjs +19 -13
  18. package/dist/cjs/router.cjs.map +1 -1
  19. package/dist/cjs/useNavigate.cjs +9 -5
  20. package/dist/cjs/useNavigate.cjs.map +1 -1
  21. package/dist/esm/Asset.d.ts +2 -0
  22. package/dist/esm/Asset.js +41 -0
  23. package/dist/esm/Asset.js.map +1 -0
  24. package/dist/esm/HeadContent.d.ts +7 -0
  25. package/dist/esm/HeadContent.js +122 -0
  26. package/dist/esm/HeadContent.js.map +1 -0
  27. package/dist/esm/Matches.d.ts +1 -0
  28. package/dist/esm/Matches.js.map +1 -1
  29. package/dist/esm/Scripts.d.ts +1 -0
  30. package/dist/esm/Scripts.js +50 -0
  31. package/dist/esm/Scripts.js.map +1 -0
  32. package/dist/esm/index.d.ts +3 -0
  33. package/dist/esm/index.js +6 -0
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/route.d.ts +10 -7
  36. package/dist/esm/route.js.map +1 -1
  37. package/dist/esm/router.js +19 -13
  38. package/dist/esm/router.js.map +1 -1
  39. package/dist/esm/useNavigate.js +9 -5
  40. package/dist/esm/useNavigate.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/Asset.tsx +40 -0
  43. package/src/HeadContent.tsx +151 -0
  44. package/src/Matches.tsx +1 -0
  45. package/src/Scripts.tsx +64 -0
  46. package/src/index.tsx +4 -0
  47. package/src/route.ts +71 -32
  48. package/src/router.ts +14 -8
  49. package/src/useNavigate.tsx +15 -6
@@ -12,12 +12,16 @@ function useNavigate(_defaultOpts) {
12
12
  );
13
13
  }
14
14
  function Navigate(props) {
15
- const { navigate } = useRouter();
15
+ const router = useRouter();
16
+ const previousPropsRef = React.useRef(null);
16
17
  React.useEffect(() => {
17
- navigate({
18
- ...props
19
- });
20
- }, [navigate, props]);
18
+ if (previousPropsRef.current !== props) {
19
+ router.navigate({
20
+ ...props
21
+ });
22
+ previousPropsRef.current = props;
23
+ }
24
+ }, [router, props]);
21
25
  return null;
22
26
  }
23
27
  export {
@@ -1 +1 @@
1
- {"version":3,"file":"useNavigate.js","sources":["../../src/useNavigate.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type { FromPathOption, NavigateOptions } from './link'\nimport type { AnyRouter, RegisteredRouter } from './router'\n\nexport type UseNavigateResult<TDefaultFrom extends string> = <\n TRouter extends RegisteredRouter,\n TTo extends string | undefined,\n TFrom extends string = TDefaultFrom,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '',\n>({\n from,\n ...rest\n}: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<void>\n\nexport function useNavigate<\n TRouter extends AnyRouter = RegisteredRouter,\n TDefaultFrom extends string = string,\n>(_defaultOpts?: {\n from?: FromPathOption<TRouter, TDefaultFrom>\n}): UseNavigateResult<TDefaultFrom> {\n const { navigate } = useRouter()\n\n return React.useCallback(\n (options: NavigateOptions) => {\n return navigate({\n ...options,\n })\n },\n [navigate],\n ) as UseNavigateResult<TDefaultFrom>\n}\n\n// NOTE: I don't know of anyone using this. It's undocumented, so let's wait until someone needs it\n// export function typedNavigate<\n// TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],\n// TDefaultFrom extends RoutePaths<TRouteTree> = '/',\n// >(navigate: (opts: NavigateOptions<any>) => Promise<void>) {\n// return navigate as <\n// TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,\n// TTo extends string = '',\n// TMaskFrom extends RoutePaths<TRouteTree> = '/',\n// TMaskTo extends string = '',\n// >(\n// opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,\n// ) => Promise<void>\n// } //\n\nexport function Navigate<\n TRouter extends AnyRouter = RegisteredRouter,\n const TFrom extends string = string,\n const TTo extends string | undefined = undefined,\n const TMaskFrom extends string = TFrom,\n const TMaskTo extends string = '',\n>(props: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>): null {\n const { navigate } = useRouter()\n\n React.useEffect(() => {\n navigate({\n ...props,\n } as any)\n }, [navigate, props])\n\n return null\n}\n"],"names":[],"mappings":";;AAgBO,SAAS,YAGd,cAEkC;AAC5B,QAAA,EAAE,SAAS,IAAI,UAAU;AAE/B,SAAO,MAAM;AAAA,IACX,CAAC,YAA6B;AAC5B,aAAO,SAAS;AAAA,QACd,GAAG;AAAA,MAAA,CACJ;AAAA,IACH;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AACF;AAiBO,SAAS,SAMd,OAAuE;AACjE,QAAA,EAAE,SAAS,IAAI,UAAU;AAE/B,QAAM,UAAU,MAAM;AACX,aAAA;AAAA,MACP,GAAG;AAAA,IAAA,CACG;AAAA,EAAA,GACP,CAAC,UAAU,KAAK,CAAC;AAEb,SAAA;AACT;"}
1
+ {"version":3,"file":"useNavigate.js","sources":["../../src/useNavigate.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type { FromPathOption, NavigateOptions } from './link'\nimport type { AnyRouter, RegisteredRouter } from './router'\n\nexport type UseNavigateResult<TDefaultFrom extends string> = <\n TRouter extends RegisteredRouter,\n TTo extends string | undefined,\n TFrom extends string = TDefaultFrom,\n TMaskFrom extends string = TFrom,\n TMaskTo extends string = '',\n>({\n from,\n ...rest\n}: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<void>\n\nexport function useNavigate<\n TRouter extends AnyRouter = RegisteredRouter,\n TDefaultFrom extends string = string,\n>(_defaultOpts?: {\n from?: FromPathOption<TRouter, TDefaultFrom>\n}): UseNavigateResult<TDefaultFrom> {\n const { navigate } = useRouter()\n\n return React.useCallback(\n (options: NavigateOptions) => {\n return navigate({\n ...options,\n })\n },\n [navigate],\n ) as UseNavigateResult<TDefaultFrom>\n}\n\n// NOTE: I don't know of anyone using this. It's undocumented, so let's wait until someone needs it\n// export function typedNavigate<\n// TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],\n// TDefaultFrom extends RoutePaths<TRouteTree> = '/',\n// >(navigate: (opts: NavigateOptions<any>) => Promise<void>) {\n// return navigate as <\n// TFrom extends RoutePaths<TRouteTree> = TDefaultFrom,\n// TTo extends string = '',\n// TMaskFrom extends RoutePaths<TRouteTree> = '/',\n// TMaskTo extends string = '',\n// >(\n// opts?: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,\n// ) => Promise<void>\n// } //\n\nexport function Navigate<\n TRouter extends AnyRouter = RegisteredRouter,\n const TFrom extends string = string,\n const TTo extends string | undefined = undefined,\n const TMaskFrom extends string = TFrom,\n const TMaskTo extends string = '',\n>(props: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>): null {\n const router = useRouter()\n\n const previousPropsRef = React.useRef<NavigateOptions<\n TRouter,\n TFrom,\n TTo,\n TMaskFrom,\n TMaskTo\n > | null>(null)\n React.useEffect(() => {\n if (previousPropsRef.current !== props) {\n router.navigate({\n ...props,\n })\n previousPropsRef.current = props\n }\n }, [router, props])\n return null\n}\n"],"names":[],"mappings":";;AAgBO,SAAS,YAGd,cAEkC;AAC5B,QAAA,EAAE,SAAS,IAAI,UAAU;AAE/B,SAAO,MAAM;AAAA,IACX,CAAC,YAA6B;AAC5B,aAAO,SAAS;AAAA,QACd,GAAG;AAAA,MAAA,CACJ;AAAA,IACH;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AACF;AAiBO,SAAS,SAMd,OAAuE;AACvE,QAAM,SAAS,UAAU;AAEnB,QAAA,mBAAmB,MAAM,OAMrB,IAAI;AACd,QAAM,UAAU,MAAM;AAChB,QAAA,iBAAiB,YAAY,OAAO;AACtC,aAAO,SAAS;AAAA,QACd,GAAG;AAAA,MAAA,CACJ;AACD,uBAAiB,UAAU;AAAA,IAAA;AAAA,EAC7B,GACC,CAAC,QAAQ,KAAK,CAAC;AACX,SAAA;AACT;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.104.1",
3
+ "version": "1.105.5",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
package/src/Asset.tsx ADDED
@@ -0,0 +1,40 @@
1
+ import type { RouterManagedTag } from '@tanstack/router-core'
2
+
3
+ export function Asset({ tag, attrs, children }: RouterManagedTag): any {
4
+ switch (tag) {
5
+ case 'title':
6
+ return (
7
+ <title {...attrs} suppressHydrationWarning>
8
+ {children}
9
+ </title>
10
+ )
11
+ case 'meta':
12
+ return <meta {...attrs} suppressHydrationWarning />
13
+ case 'link':
14
+ return <link {...attrs} suppressHydrationWarning />
15
+ case 'style':
16
+ return (
17
+ <style
18
+ {...attrs}
19
+ dangerouslySetInnerHTML={{ __html: children as any }}
20
+ />
21
+ )
22
+ case 'script':
23
+ if ((attrs as any) && (attrs as any).src) {
24
+ return <script {...attrs} suppressHydrationWarning />
25
+ }
26
+ if (typeof children === 'string')
27
+ return (
28
+ <script
29
+ {...attrs}
30
+ dangerouslySetInnerHTML={{
31
+ __html: children,
32
+ }}
33
+ suppressHydrationWarning
34
+ />
35
+ )
36
+ return null
37
+ default:
38
+ return null
39
+ }
40
+ }
@@ -0,0 +1,151 @@
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
+ 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
+ })) as Array<RouterManagedTag>,
72
+ structuralSharing: true as any,
73
+ })
74
+
75
+ const preloadMeta = useRouterState({
76
+ select: (state) => {
77
+ const preloadMeta: Array<RouterManagedTag> = []
78
+
79
+ state.matches
80
+ .map((match) => router.looseRoutesById[match.routeId]!)
81
+ .forEach((route) =>
82
+ router.ssr?.manifest?.routes[route.id]?.preloads
83
+ ?.filter(Boolean)
84
+ .forEach((preload) => {
85
+ preloadMeta.push({
86
+ tag: 'link',
87
+ attrs: {
88
+ rel: 'modulepreload',
89
+ href: preload,
90
+ },
91
+ })
92
+ }),
93
+ )
94
+
95
+ return preloadMeta
96
+ },
97
+ structuralSharing: true as any,
98
+ })
99
+
100
+ const headScripts = useRouterState({
101
+ select: (state) =>
102
+ (
103
+ state.matches
104
+ .map((match) => match.headScripts!)
105
+ .flat(1)
106
+ .filter(Boolean) as Array<RouterManagedTag>
107
+ ).map(({ children, ...script }) => ({
108
+ tag: 'script',
109
+ attrs: {
110
+ ...script,
111
+ },
112
+ children,
113
+ })),
114
+ structuralSharing: true as any,
115
+ })
116
+
117
+ return uniqBy(
118
+ [
119
+ ...meta,
120
+ ...preloadMeta,
121
+ ...links,
122
+ ...headScripts,
123
+ ] as Array<RouterManagedTag>,
124
+ (d) => {
125
+ return JSON.stringify(d)
126
+ },
127
+ )
128
+ }
129
+
130
+ /**
131
+ * @description The `HeadContent` component is used to render meta tags, links, and scripts for the current route.
132
+ * It should be rendered in the `<head>` of your document.
133
+ */
134
+ export function HeadContent() {
135
+ const tags = useTags()
136
+ return tags.map((tag) => (
137
+ <Asset {...tag} key={`tsr-meta-${JSON.stringify(tag)}`} />
138
+ ))
139
+ }
140
+
141
+ function uniqBy<T>(arr: Array<T>, fn: (item: T) => string) {
142
+ const seen = new Set<string>()
143
+ return arr.filter((item) => {
144
+ const key = fn(item)
145
+ if (seen.has(key)) {
146
+ return false
147
+ }
148
+ seen.add(key)
149
+ return true
150
+ })
151
+ }
package/src/Matches.tsx CHANGED
@@ -88,6 +88,7 @@ export interface RouteMatch<
88
88
  meta?: Array<React.JSX.IntrinsicElements['meta'] | undefined>
89
89
  links?: Array<React.JSX.IntrinsicElements['link'] | undefined>
90
90
  scripts?: Array<React.JSX.IntrinsicElements['script'] | undefined>
91
+ headScripts?: Array<React.JSX.IntrinsicElements['script'] | undefined>
91
92
  headers?: Record<string, string>
92
93
  globalNotFound?: boolean
93
94
  staticData: StaticDataRouteOption
@@ -0,0 +1,64 @@
1
+ import { Asset } from './Asset'
2
+ import { useRouterState } from './useRouterState'
3
+ import { useRouter } from './useRouter'
4
+ import type { RouterManagedTag } from '@tanstack/router-core'
5
+
6
+ export const Scripts = () => {
7
+ const router = useRouter()
8
+
9
+ const assetScripts = useRouterState({
10
+ select: (state) => {
11
+ const assetScripts: Array<RouterManagedTag> = []
12
+ const manifest = router.ssr?.manifest
13
+
14
+ if (!manifest) {
15
+ return []
16
+ }
17
+
18
+ state.matches
19
+ .map((match) => router.looseRoutesById[match.routeId]!)
20
+ .forEach((route) =>
21
+ manifest.routes[route.id]?.assets
22
+ ?.filter((d) => d.tag === 'script')
23
+ .forEach((asset) => {
24
+ assetScripts.push({
25
+ tag: 'script',
26
+ attrs: asset.attrs,
27
+ children: asset.children,
28
+ } as any)
29
+ }),
30
+ )
31
+
32
+ return assetScripts
33
+ },
34
+ structuralSharing: true as any,
35
+ })
36
+
37
+ const { scripts } = useRouterState({
38
+ select: (state) => ({
39
+ scripts: (
40
+ state.matches
41
+ .map((match) => match.scripts!)
42
+ .flat(1)
43
+ .filter(Boolean) as Array<RouterManagedTag>
44
+ ).map(({ children, ...script }) => ({
45
+ tag: 'script',
46
+ attrs: {
47
+ ...script,
48
+ suppressHydrationWarning: true,
49
+ },
50
+ children,
51
+ })),
52
+ }),
53
+ })
54
+
55
+ const allScripts = [...scripts, ...assetScripts] as Array<RouterManagedTag>
56
+
57
+ return (
58
+ <>
59
+ {allScripts.map((asset, i) => (
60
+ <Asset {...asset} key={`tsr-scripts-${asset.tag}-${i}`} />
61
+ ))}
62
+ </>
63
+ )
64
+ }
package/src/index.tsx CHANGED
@@ -356,3 +356,7 @@ export type { NotFoundError } from './not-found'
356
356
  export * from './typePrimitives'
357
357
 
358
358
  export { ScriptOnce } from './ScriptOnce'
359
+
360
+ export { Asset } from './Asset'
361
+ export { HeadContent } from './HeadContent'
362
+ export { Scripts } from './Scripts'
package/src/route.ts CHANGED
@@ -325,6 +325,51 @@ export interface BeforeLoadContextOptions<
325
325
  >
326
326
  }
327
327
 
328
+ type AssetFnContextOptions<
329
+ in out TRouteId,
330
+ in out TFullPath,
331
+ in out TParentRoute extends AnyRoute,
332
+ in out TParams,
333
+ in out TSearchValidator,
334
+ in out TLoaderFn,
335
+ in out TRouterContext,
336
+ in out TRouteContextFn,
337
+ in out TBeforeLoadFn,
338
+ in out TLoaderDeps,
339
+ > = {
340
+ matches: Array<
341
+ RouteMatch<
342
+ TRouteId,
343
+ TFullPath,
344
+ ResolveAllParamsFromParent<TParentRoute, TParams>,
345
+ ResolveFullSearchSchema<TParentRoute, TSearchValidator>,
346
+ ResolveLoaderData<TLoaderFn>,
347
+ ResolveAllContext<
348
+ TParentRoute,
349
+ TRouterContext,
350
+ TRouteContextFn,
351
+ TBeforeLoadFn
352
+ >,
353
+ TLoaderDeps
354
+ >
355
+ >
356
+ match: RouteMatch<
357
+ TRouteId,
358
+ TFullPath,
359
+ ResolveAllParamsFromParent<TParentRoute, TParams>,
360
+ ResolveFullSearchSchema<TParentRoute, TSearchValidator>,
361
+ ResolveLoaderData<TLoaderFn>,
362
+ ResolveAllContext<
363
+ TParentRoute,
364
+ TRouterContext,
365
+ TRouteContextFn,
366
+ TBeforeLoadFn
367
+ >,
368
+ TLoaderDeps
369
+ >
370
+ params: ResolveAllParamsFromParent<TParentRoute, TParams>
371
+ loaderData: ResolveLoaderData<TLoaderFn>
372
+ }
328
373
  export interface UpdatableRouteOptions<
329
374
  in out TParentRoute extends AnyRoute,
330
375
  in out TRouteId,
@@ -427,44 +472,38 @@ export interface UpdatableRouteOptions<
427
472
  headers?: (ctx: {
428
473
  loaderData: ResolveLoaderData<TLoaderFn>
429
474
  }) => Record<string, string>
430
- head?: (ctx: {
431
- matches: Array<
432
- RouteMatch<
433
- TRouteId,
434
- TFullPath,
435
- ResolveAllParamsFromParent<TParentRoute, TParams>,
436
- ResolveFullSearchSchema<TParentRoute, TSearchValidator>,
437
- ResolveLoaderData<TLoaderFn>,
438
- ResolveAllContext<
439
- TParentRoute,
440
- TRouterContext,
441
- TRouteContextFn,
442
- TBeforeLoadFn
443
- >,
444
- TLoaderDeps
445
- >
446
- >
447
- match: RouteMatch<
475
+ head?: (
476
+ ctx: AssetFnContextOptions<
448
477
  TRouteId,
449
478
  TFullPath,
450
- ResolveAllParamsFromParent<TParentRoute, TParams>,
451
- ResolveFullSearchSchema<TParentRoute, TSearchValidator>,
452
- ResolveLoaderData<TLoaderFn>,
453
- ResolveAllContext<
454
- TParentRoute,
455
- TRouterContext,
456
- TRouteContextFn,
457
- TBeforeLoadFn
458
- >,
479
+ TParentRoute,
480
+ TParams,
481
+ TSearchValidator,
482
+ TLoaderFn,
483
+ TRouterContext,
484
+ TRouteContextFn,
485
+ TBeforeLoadFn,
459
486
  TLoaderDeps
460
- >
461
- params: ResolveAllParamsFromParent<TParentRoute, TParams>
462
- loaderData: ResolveLoaderData<TLoaderFn>
463
- }) => {
487
+ >,
488
+ ) => {
464
489
  links?: AnyRouteMatch['links']
465
- scripts?: AnyRouteMatch['scripts']
490
+ scripts?: AnyRouteMatch['headScripts']
466
491
  meta?: AnyRouteMatch['meta']
467
492
  }
493
+ scripts?: (
494
+ ctx: AssetFnContextOptions<
495
+ TRouteId,
496
+ TFullPath,
497
+ TParentRoute,
498
+ TParams,
499
+ TSearchValidator,
500
+ TLoaderFn,
501
+ TRouterContext,
502
+ TRouteContextFn,
503
+ TBeforeLoadFn,
504
+ TLoaderDeps
505
+ >,
506
+ ) => AnyRouteMatch['scripts']
468
507
  ssr?: boolean
469
508
  codeSplitGroupings?: Array<
470
509
  Array<
package/src/router.ts CHANGED
@@ -1311,6 +1311,7 @@ export class Router<
1311
1311
  preload: false,
1312
1312
  links: undefined,
1313
1313
  scripts: undefined,
1314
+ headScripts: undefined,
1314
1315
  meta: undefined,
1315
1316
  staticData: route.options.staticData || {},
1316
1317
  loadPromise: createControlledPromise(),
@@ -1378,16 +1379,17 @@ export class Router<
1378
1379
  match.headers = route.options.headers?.({
1379
1380
  loaderData: match.loaderData,
1380
1381
  })
1381
- const headFnContent = route.options.head?.({
1382
+ const assetContext = {
1382
1383
  matches,
1383
1384
  match,
1384
1385
  params: match.params,
1385
1386
  loaderData: match.loaderData,
1386
- })
1387
-
1387
+ }
1388
+ const headFnContent = route.options.head?.(assetContext)
1388
1389
  match.links = headFnContent?.links
1389
- match.scripts = headFnContent?.scripts
1390
+ match.headScripts = headFnContent?.scripts
1390
1391
  match.meta = headFnContent?.meta
1392
+ match.scripts = route.options.scripts?.(assetContext)
1391
1393
  }
1392
1394
  })
1393
1395
 
@@ -2557,16 +2559,19 @@ export class Router<
2557
2559
 
2558
2560
  await potentialPendingMinPromise()
2559
2561
 
2560
- const headFnContent = route.options.head?.({
2562
+ const assetContext = {
2561
2563
  matches,
2562
2564
  match: this.getMatch(matchId)!,
2563
2565
  params: this.getMatch(matchId)!.params,
2564
2566
  loaderData,
2565
- })
2567
+ }
2568
+ const headFnContent =
2569
+ route.options.head?.(assetContext)
2566
2570
  const meta = headFnContent?.meta
2567
2571
  const links = headFnContent?.links
2568
- const scripts = headFnContent?.scripts
2572
+ const headScripts = headFnContent?.scripts
2569
2573
 
2574
+ const scripts = route.options.scripts?.(assetContext)
2570
2575
  const headers = route.options.headers?.({
2571
2576
  loaderData,
2572
2577
  })
@@ -2580,8 +2585,9 @@ export class Router<
2580
2585
  loaderData,
2581
2586
  meta,
2582
2587
  links,
2583
- scripts,
2588
+ headScripts,
2584
2589
  headers,
2590
+ scripts,
2585
2591
  }))
2586
2592
  } catch (e) {
2587
2593
  let error = e
@@ -54,13 +54,22 @@ export function Navigate<
54
54
  const TMaskFrom extends string = TFrom,
55
55
  const TMaskTo extends string = '',
56
56
  >(props: NavigateOptions<TRouter, TFrom, TTo, TMaskFrom, TMaskTo>): null {
57
- const { navigate } = useRouter()
57
+ const router = useRouter()
58
58
 
59
+ const previousPropsRef = React.useRef<NavigateOptions<
60
+ TRouter,
61
+ TFrom,
62
+ TTo,
63
+ TMaskFrom,
64
+ TMaskTo
65
+ > | null>(null)
59
66
  React.useEffect(() => {
60
- navigate({
61
- ...props,
62
- } as any)
63
- }, [navigate, props])
64
-
67
+ if (previousPropsRef.current !== props) {
68
+ router.navigate({
69
+ ...props,
70
+ })
71
+ previousPropsRef.current = props
72
+ }
73
+ }, [router, props])
65
74
  return null
66
75
  }