@tanstack/react-router 1.33.8 → 1.34.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.
@@ -1,16 +1,46 @@
1
1
  import * as React from "react";
2
2
  import { useRouter } from "./useRouter.js";
3
- function useBlocker(blockerFn, condition = true) {
3
+ function useBlocker(blockerFnOrOpts, condition) {
4
+ const { blockerFn, blockerCondition } = blockerFnOrOpts ? typeof blockerFnOrOpts === "function" ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true } : {
5
+ blockerFn: blockerFnOrOpts.blockerFn,
6
+ blockerCondition: blockerFnOrOpts.condition ?? true
7
+ } : { blockerFn: void 0, blockerCondition: condition ?? true };
4
8
  const { history } = useRouter();
5
- React.useEffect(() => {
6
- if (!condition)
7
- return;
8
- return history.block(blockerFn);
9
+ const [resolver, setResolver] = React.useState({
10
+ status: "idle",
11
+ proceed: () => {
12
+ },
13
+ reset: () => {
14
+ }
15
+ });
16
+ const createPromise = () => new Promise((resolve) => {
17
+ setResolver({
18
+ status: "idle",
19
+ proceed: () => resolve(true),
20
+ reset: () => resolve(false)
21
+ });
9
22
  });
23
+ const [promise, setPromise] = React.useState(createPromise);
24
+ React.useEffect(() => {
25
+ const blockerFnComposed = async () => {
26
+ if (blockerFn) {
27
+ return await blockerFn();
28
+ }
29
+ setResolver((prev) => ({
30
+ ...prev,
31
+ status: "blocked"
32
+ }));
33
+ const canNavigateAsync = await promise;
34
+ setPromise(createPromise);
35
+ return canNavigateAsync;
36
+ };
37
+ return !blockerCondition ? void 0 : history.block(blockerFnComposed);
38
+ }, [blockerFn, blockerCondition, history, promise]);
39
+ return resolver;
10
40
  }
11
- function Block({ blocker, condition, children }) {
12
- useBlocker(blocker, condition);
13
- return children ?? null;
41
+ function Block({ blockerFn, condition, children }) {
42
+ const resolver = useBlocker({ blockerFn, condition });
43
+ return children ? typeof children === "function" ? children(resolver) : children : null;
14
44
  }
15
45
  export {
16
46
  Block,
@@ -1 +1 @@
1
- {"version":3,"file":"useBlocker.js","sources":["../../src/useBlocker.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type { BlockerFn } from '@tanstack/history'\nimport type { ReactNode } from './route'\n\nexport function useBlocker(\n blockerFn: BlockerFn,\n condition: boolean | any = true,\n): void {\n const { history } = useRouter()\n\n React.useEffect(() => {\n if (!condition) return\n return history.block(blockerFn)\n })\n}\n\nexport function Block({ blocker, condition, children }: PromptProps) {\n useBlocker(blocker, condition)\n return children ?? null\n}\n\nexport type PromptProps = {\n blocker: BlockerFn\n condition?: boolean | any\n children?: ReactNode\n}\n"],"names":[],"mappings":";;AAKgB,SAAA,WACd,WACA,YAA2B,MACrB;AACA,QAAA,EAAE,YAAY;AAEpB,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC;AAAW;AACT,WAAA,QAAQ,MAAM,SAAS;AAAA,EAAA,CAC/B;AACH;AAEO,SAAS,MAAM,EAAE,SAAS,WAAW,YAAyB;AACnE,aAAW,SAAS,SAAS;AAC7B,SAAO,YAAY;AACrB;"}
1
+ {"version":3,"file":"useBlocker.js","sources":["../../src/useBlocker.tsx"],"sourcesContent":["import * as React from 'react'\nimport { useRouter } from './useRouter'\nimport type { BlockerFn } from '@tanstack/history'\nimport type { ReactNode } from './route'\n\ntype BlockerResolver = {\n status: 'idle' | 'blocked'\n proceed: () => void\n reset: () => void\n}\n\ntype BlockerOpts = {\n blockerFn?: BlockerFn\n condition?: boolean | any\n}\n\nexport function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver\n\n/**\n * @deprecated Use the BlockerOpts object syntax instead\n */\nexport function useBlocker(\n blockerFn?: BlockerFn,\n condition?: boolean | any,\n): BlockerResolver\n\nexport function useBlocker(\n blockerFnOrOpts?: BlockerFn | BlockerOpts,\n condition?: boolean | any,\n): BlockerResolver {\n const { blockerFn, blockerCondition } = blockerFnOrOpts\n ? typeof blockerFnOrOpts === 'function'\n ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true }\n : {\n blockerFn: blockerFnOrOpts.blockerFn,\n blockerCondition: blockerFnOrOpts.condition ?? true,\n }\n : { blockerFn: undefined, blockerCondition: condition ?? true }\n const { history } = useRouter()\n\n const [resolver, setResolver] = React.useState<BlockerResolver>({\n status: 'idle',\n proceed: () => {},\n reset: () => {},\n })\n\n const createPromise = () =>\n new Promise<boolean>((resolve) => {\n setResolver({\n status: 'idle',\n proceed: () => resolve(true),\n reset: () => resolve(false),\n })\n })\n\n const [promise, setPromise] = React.useState(createPromise)\n\n React.useEffect(() => {\n const blockerFnComposed = async () => {\n // If a function is provided, it takes precedence over the promise blocker\n if (blockerFn) {\n return await blockerFn()\n }\n\n setResolver((prev) => ({\n ...prev,\n status: 'blocked',\n }))\n const canNavigateAsync = await promise\n\n setPromise(createPromise)\n\n return canNavigateAsync\n }\n\n return !blockerCondition ? undefined : history.block(blockerFnComposed)\n }, [blockerFn, blockerCondition, history, promise])\n\n return resolver\n}\n\nexport function Block({ blockerFn, condition, children }: PromptProps) {\n const resolver = useBlocker({ blockerFn, condition })\n return children\n ? typeof children === 'function'\n ? children(resolver)\n : children\n : null\n}\n\nexport type PromptProps = {\n blockerFn?: BlockerFn\n condition?: boolean | any\n children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode)\n}\n"],"names":[],"mappings":";;AA0BgB,SAAA,WACd,iBACA,WACiB;AACjB,QAAM,EAAE,WAAW,qBAAqB,kBACpC,OAAO,oBAAoB,aACzB,EAAE,WAAW,iBAAiB,kBAAkB,aAAa,SAC7D;AAAA,IACE,WAAW,gBAAgB;AAAA,IAC3B,kBAAkB,gBAAgB,aAAa;AAAA,EAAA,IAEnD,EAAE,WAAW,QAAW,kBAAkB,aAAa;AACrD,QAAA,EAAE,YAAY;AAEpB,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAA0B;AAAA,IAC9D,QAAQ;AAAA,IACR,SAAS,MAAM;AAAA,IAAC;AAAA,IAChB,OAAO,MAAM;AAAA,IAAC;AAAA,EAAA,CACf;AAED,QAAM,gBAAgB,MACpB,IAAI,QAAiB,CAAC,YAAY;AACpB,gBAAA;AAAA,MACV,QAAQ;AAAA,MACR,SAAS,MAAM,QAAQ,IAAI;AAAA,MAC3B,OAAO,MAAM,QAAQ,KAAK;AAAA,IAAA,CAC3B;AAAA,EAAA,CACF;AAEH,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,aAAa;AAE1D,QAAM,UAAU,MAAM;AACpB,UAAM,oBAAoB,YAAY;AAEpC,UAAI,WAAW;AACb,eAAO,MAAM,UAAU;AAAA,MACzB;AAEA,kBAAY,CAAC,UAAU;AAAA,QACrB,GAAG;AAAA,QACH,QAAQ;AAAA,MACR,EAAA;AACF,YAAM,mBAAmB,MAAM;AAE/B,iBAAW,aAAa;AAEjB,aAAA;AAAA,IAAA;AAGT,WAAO,CAAC,mBAAmB,SAAY,QAAQ,MAAM,iBAAiB;AAAA,KACrE,CAAC,WAAW,kBAAkB,SAAS,OAAO,CAAC;AAE3C,SAAA;AACT;AAEO,SAAS,MAAM,EAAE,WAAW,WAAW,YAAyB;AACrE,QAAM,WAAW,WAAW,EAAE,WAAW,UAAW,CAAA;AACpD,SAAO,WACH,OAAO,aAAa,aAClB,SAAS,QAAQ,IACjB,WACF;AACN;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
- "version": "1.33.8",
3
+ "version": "1.34.1",
4
4
  "description": "",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -23,7 +23,9 @@ export function Transitioner() {
23
23
  routerState.isLoading || isTransitioning || hasPendingMatches
24
24
  const previousIsAnyPending = usePrevious(isAnyPending)
25
25
 
26
- router.startReactTransition = startReactTransition_
26
+ if (!router.isServer) {
27
+ router.startReactTransition = startReactTransition_
28
+ }
27
29
 
28
30
  // Subscribe to location changes
29
31
  // and try to load the new location
package/src/awaited.tsx CHANGED
@@ -10,7 +10,9 @@ export type AwaitOptions<T> = {
10
10
  promise: DeferredPromise<T>
11
11
  }
12
12
 
13
- export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
13
+ export function useAwaited<T>({
14
+ promise,
15
+ }: AwaitOptions<T>): [T, DeferredPromise<T>] {
14
16
  const router = useRouter()
15
17
  // const rerender = React.useReducer((x) => x + 1, 0)[1]
16
18
 
@@ -56,20 +58,6 @@ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
56
58
  throw isDehydratedDeferred(promise) ? state.promise : promise
57
59
  }
58
60
 
59
- // If we are the originator of the promise,
60
- // inject the state into the HTML stream
61
- if (!isDehydratedDeferred(promise)) {
62
- router.injectHtml(
63
- `<script class='tsr_deferred_data'>window.__TSR__DEFERRED__${state.uid} = ${JSON.stringify(router.options.transformer.stringify(state))}
64
- if (window.__TSR__ROUTER__) {
65
- let deferred = window.__TSR__ROUTER__.getDeferred('${state.uid}');
66
- if (deferred) deferred.resolve(window.__TSR__DEFERRED__${state.uid});
67
- }
68
- document.querySelectorAll('.tsr_deferred_data').forEach((el) => el.parentElement.removeChild(el));
69
- </script>`,
70
- )
71
- }
72
-
73
61
  if (state.status === 'error') {
74
62
  if (typeof document !== 'undefined') {
75
63
  if (isServerSideError(state.error)) {
@@ -93,7 +81,7 @@ export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
93
81
  }
94
82
  }
95
83
 
96
- return [promise.__deferredState.data as any]
84
+ return [promise.__deferredState.data as any, promise]
97
85
  }
98
86
 
99
87
  export function Await<T>(
@@ -115,6 +103,40 @@ function AwaitInner<T>(
115
103
  children: (result: T) => React.ReactNode
116
104
  },
117
105
  ) {
118
- const awaited = useAwaited(props)
119
- return props.children(...awaited) as React.JSX.Element
106
+ const router = useRouter()
107
+ const [data, promise] = useAwaited(props)
108
+ const state = promise.__deferredState
109
+ // If we are the originator of the promise,
110
+ // inject the state into the HTML stream
111
+ return (
112
+ <>
113
+ {!isDehydratedDeferred(promise) ? (
114
+ <ScriptOnce
115
+ children={`window.__TSR__DEFERRED__${state.uid} = ${JSON.stringify(router.options.transformer.stringify(state))}
116
+ if (window.__TSR__ROUTER__) {
117
+ let deferred = window.__TSR__ROUTER__.getDeferred('${state.uid}');
118
+ if (deferred) deferred.resolve(window.__TSR__DEFERRED__${state.uid});
119
+ }
120
+ document.querySelectorAll('.tsr-script-once').forEach((el) => el.parentElement.removeChild(el));`}
121
+ />
122
+ ) : null}
123
+ {props.children(data)}
124
+ </>
125
+ )
126
+ }
127
+
128
+ function ScriptOnce({
129
+ className,
130
+ children,
131
+ ...rest
132
+ }: { children: string } & React.HTMLProps<HTMLScriptElement>) {
133
+ return (
134
+ <script
135
+ {...rest}
136
+ className={`tsr-script-once ${className || ''}`}
137
+ dangerouslySetInnerHTML={{
138
+ __html: children,
139
+ }}
140
+ />
141
+ )
120
142
  }
package/src/router.ts CHANGED
@@ -483,7 +483,6 @@ export class Router<
483
483
  shouldViewTransition?: boolean = undefined
484
484
  latestLoadPromise: Promise<void> = Promise.resolve()
485
485
  subscribers = new Set<RouterListener<RouterEvent>>()
486
- injectedHtml: Array<InjectedHtmlEntry> = []
487
486
  dehydratedData?: TDehydrated
488
487
  viewTransitionPromise?: ControlledPromise<true>
489
488
  manifest?: Manifest
@@ -2231,9 +2230,10 @@ export class Router<
2231
2230
  return match
2232
2231
  }
2233
2232
 
2234
- injectHtml = async (html: string | (() => Promise<string> | string)) => {
2235
- this.injectedHtml.push(html)
2236
- }
2233
+ /**
2234
+ * @deprecated Injecting HTML directly is no longer supported. Use the new <ScriptOnce /> component instead.
2235
+ */
2236
+ injectHtml = async (html: string | (() => Promise<string> | string)) => {}
2237
2237
 
2238
2238
  // We use a token -> weak map to keep track of deferred promises
2239
2239
  // that are registered on the server and need to be resolved
@@ -2250,55 +2250,6 @@ export class Router<
2250
2250
  return this.registeredDeferreds.get(token)
2251
2251
  }
2252
2252
 
2253
- /**
2254
- * @deprecated Please inject your own html using the `injectHtml` method
2255
- */
2256
- dehydrateData = <T>(key: any, getData: T | (() => Promise<T> | T)) => {
2257
- warning(
2258
- false,
2259
- `The dehydrateData method is deprecated. Please use the injectHtml method to inject your own data.`,
2260
- )
2261
-
2262
- if (typeof document === 'undefined') {
2263
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
2264
-
2265
- this.injectHtml(async () => {
2266
- const id = `__TSR_DEHYDRATED__${strKey}`
2267
- const data =
2268
- typeof getData === 'function' ? await (getData as any)() : getData
2269
- return `<script id='${id}' suppressHydrationWarning>
2270
- window["__TSR_DEHYDRATED__${escapeJSON(
2271
- strKey,
2272
- )}"] = ${JSON.stringify(this.options.transformer.stringify(data))}
2273
- </script>`
2274
- })
2275
-
2276
- return () => this.hydrateData<T>(key)
2277
- }
2278
-
2279
- return () => undefined
2280
- }
2281
-
2282
- /**
2283
- * @deprecated Please extract your own data from scripts injected using the `injectHtml` method
2284
- */
2285
- hydrateData = <T = unknown>(key: any) => {
2286
- warning(
2287
- false,
2288
- `The hydrateData method is deprecated. Please use the extractHtml method to extract your own data.`,
2289
- )
2290
-
2291
- if (typeof document !== 'undefined') {
2292
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
2293
-
2294
- return this.options.transformer.parse(
2295
- window[`__TSR_DEHYDRATED__${strKey}` as any] as unknown as string,
2296
- ) as T
2297
- }
2298
-
2299
- return undefined
2300
- }
2301
-
2302
2253
  dehydrate = (): DehydratedRouter => {
2303
2254
  const pickError =
2304
2255
  this.options.errorSerializer?.serialize ?? defaultSerializeError
@@ -3,25 +3,93 @@ import { useRouter } from './useRouter'
3
3
  import type { BlockerFn } from '@tanstack/history'
4
4
  import type { ReactNode } from './route'
5
5
 
6
+ type BlockerResolver = {
7
+ status: 'idle' | 'blocked'
8
+ proceed: () => void
9
+ reset: () => void
10
+ }
11
+
12
+ type BlockerOpts = {
13
+ blockerFn?: BlockerFn
14
+ condition?: boolean | any
15
+ }
16
+
17
+ export function useBlocker(blockerFnOrOpts?: BlockerOpts): BlockerResolver
18
+
19
+ /**
20
+ * @deprecated Use the BlockerOpts object syntax instead
21
+ */
22
+ export function useBlocker(
23
+ blockerFn?: BlockerFn,
24
+ condition?: boolean | any,
25
+ ): BlockerResolver
26
+
6
27
  export function useBlocker(
7
- blockerFn: BlockerFn,
8
- condition: boolean | any = true,
9
- ): void {
28
+ blockerFnOrOpts?: BlockerFn | BlockerOpts,
29
+ condition?: boolean | any,
30
+ ): BlockerResolver {
31
+ const { blockerFn, blockerCondition } = blockerFnOrOpts
32
+ ? typeof blockerFnOrOpts === 'function'
33
+ ? { blockerFn: blockerFnOrOpts, blockerCondition: condition ?? true }
34
+ : {
35
+ blockerFn: blockerFnOrOpts.blockerFn,
36
+ blockerCondition: blockerFnOrOpts.condition ?? true,
37
+ }
38
+ : { blockerFn: undefined, blockerCondition: condition ?? true }
10
39
  const { history } = useRouter()
11
40
 
12
- React.useEffect(() => {
13
- if (!condition) return
14
- return history.block(blockerFn)
41
+ const [resolver, setResolver] = React.useState<BlockerResolver>({
42
+ status: 'idle',
43
+ proceed: () => {},
44
+ reset: () => {},
15
45
  })
46
+
47
+ const createPromise = () =>
48
+ new Promise<boolean>((resolve) => {
49
+ setResolver({
50
+ status: 'idle',
51
+ proceed: () => resolve(true),
52
+ reset: () => resolve(false),
53
+ })
54
+ })
55
+
56
+ const [promise, setPromise] = React.useState(createPromise)
57
+
58
+ React.useEffect(() => {
59
+ const blockerFnComposed = async () => {
60
+ // If a function is provided, it takes precedence over the promise blocker
61
+ if (blockerFn) {
62
+ return await blockerFn()
63
+ }
64
+
65
+ setResolver((prev) => ({
66
+ ...prev,
67
+ status: 'blocked',
68
+ }))
69
+ const canNavigateAsync = await promise
70
+
71
+ setPromise(createPromise)
72
+
73
+ return canNavigateAsync
74
+ }
75
+
76
+ return !blockerCondition ? undefined : history.block(blockerFnComposed)
77
+ }, [blockerFn, blockerCondition, history, promise])
78
+
79
+ return resolver
16
80
  }
17
81
 
18
- export function Block({ blocker, condition, children }: PromptProps) {
19
- useBlocker(blocker, condition)
20
- return children ?? null
82
+ export function Block({ blockerFn, condition, children }: PromptProps) {
83
+ const resolver = useBlocker({ blockerFn, condition })
84
+ return children
85
+ ? typeof children === 'function'
86
+ ? children(resolver)
87
+ : children
88
+ : null
21
89
  }
22
90
 
23
91
  export type PromptProps = {
24
- blocker: BlockerFn
92
+ blockerFn?: BlockerFn
25
93
  condition?: boolean | any
26
- children?: ReactNode
94
+ children?: ReactNode | (({ proceed, reset }: BlockerResolver) => ReactNode)
27
95
  }