Skip to content

Commit

Permalink
fix: default to always reloading
Browse files Browse the repository at this point in the history
  • Loading branch information
tannerlinsley committed Nov 30, 2023
1 parent 681cd00 commit 76f411c
Show file tree
Hide file tree
Showing 9 changed files with 203 additions and 183 deletions.
25 changes: 13 additions & 12 deletions examples/react/basic/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ const fetchPost = async (postId: string) => {
await new Promise((r) => setTimeout(r, 500))
const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.catch((err) => {
if (err.response.status === 404) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}
throw err
})
.then((r) => r.data)

if (!post) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}

return post
}

Expand Down Expand Up @@ -134,14 +136,13 @@ class NotFoundError extends Error {}
const postRoute = new Route({
getParentRoute: () => postsRoute,
path: '$postId',
// errorComponent: false,
// errorComponent: ({ error }) => {
// if (error instanceof NotFoundError) {
// return <div>{error.message}</div>
// }

// return <ErrorComponent error={error} />
// },
errorComponent: ({ error }) => {
if (error instanceof NotFoundError) {
return <div>{error.message}</div>
}

return <ErrorComponent error={error} />
},
// Only reload the data if we are entering the route
shouldReload: ({ cause }) => cause === 'enter',
loader: ({ params }) => fetchPost(params.postId),
Expand Down
13 changes: 7 additions & 6 deletions examples/react/deferred-data/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ const fetchPost = async (postId: string) => {

const post = await axios
.get<PostType>(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.catch((err) => {
if (err.response.status === 404) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}
throw err
})
.then((r) => r.data)

if (!post) {
throw new NotFoundError(`Post with id "${postId}" not found!`)
}

return {
post,
commentsPromise: defer(commentsPromise),
Expand Down Expand Up @@ -108,10 +110,10 @@ const indexRoute = new Route({
const postsRoute = new Route({
getParentRoute: () => rootRoute,
path: 'posts',
key: false,
loader: fetchPosts,
component: ({ useLoaderData }) => {
const posts = useLoaderData()
console.log(posts)

return (
<div className="p-2 flex gap-2">
Expand Down Expand Up @@ -154,7 +156,6 @@ class NotFoundError extends Error {}
const postRoute = new Route({
getParentRoute: () => postsRoute,
path: '$postId',
key: false,
loader: async ({ params: { postId } }) => fetchPost(postId),
errorComponent: ({ error }) => {
if (error instanceof NotFoundError) {
Expand Down
2 changes: 1 addition & 1 deletion packages/react-router/src/Matches.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ResolveRelativePath, ToOptions } from './link'
import { AnyRoute, ReactNode, rootRouteId } from './route'
import { RouteById, RouteByPath, RouteIds, RoutePaths } from './routeInfo'
import { RegisteredRouter } from './router'
import { NoInfer, StrictOrFrom, functionalUpdate } from './utils'
import { NoInfer, StrictOrFrom } from './utils'

export function Matches() {
const { routesById, state } = useRouter()
Expand Down
109 changes: 85 additions & 24 deletions packages/react-router/src/RouterProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ import {
pick,
replaceEqualDeep,
useStableCallback,
escapeJSON,
} from './utils'
import { MatchRouteOptions } from './Matches'

Expand Down Expand Up @@ -116,6 +117,8 @@ export type BuildLocationFn<TRouteTree extends AnyRoute> = (
opts: BuildNextOptions,
) => ParsedLocation

export type InjectedHtmlEntry = string | (() => Promise<string> | string)

export type RouterContext<
TRouteTree extends AnyRoute,
// TDehydrated extends Record<string, any>,
Expand All @@ -132,6 +135,13 @@ export type RouterContext<
buildLocation: BuildLocationFn<TRouteTree>
subscribe: Router<TRouteTree>['subscribe']
resetNextScrollRef: React.MutableRefObject<boolean>
injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
injectHtml: (entry: InjectedHtmlEntry) => void
dehydrateData: <T>(
key: any,
getData: T | (() => Promise<T> | T),
) => () => void
hydrateData: <T>(key: any) => T | undefined
}

export const routerContext = React.createContext<RouterContext<any>>(null!)
Expand Down Expand Up @@ -986,21 +996,24 @@ export function RouterProvider<

// Default to reloading the route all the time
let shouldReload = true
let shouldReloadDeps =
typeof route.options.shouldReload === 'function'
? route.options.shouldReload?.(loaderContext)
: !!route.options.shouldReload

if (typeof shouldReloadDeps === 'object') {
// compare the deps to see if they've changed
shouldReload = !deepEqual(
shouldReloadDeps,
match.shouldReloadDeps,
)

match.shouldReloadDeps = shouldReloadDeps
} else {
shouldReload = !!shouldReloadDeps
if (cause !== 'enter') {
let shouldReloadDeps =
typeof route.options.shouldReload === 'function'
? route.options.shouldReload?.(loaderContext)
: !!(route.options.shouldReload ?? true)

if (typeof shouldReloadDeps === 'object') {
// compare the deps to see if they've changed
shouldReload = !deepEqual(
shouldReloadDeps,
match.shouldReloadDeps,
)

match.shouldReloadDeps = shouldReloadDeps
} else {
shouldReload = !!shouldReloadDeps
}
}

// If the user doesn't want the route to reload, just
Expand Down Expand Up @@ -1377,16 +1390,6 @@ export function RouterProvider<
}
}, [history])

React.useLayoutEffect(() => {
startReactTransition(() => {
try {
load()
} catch (err) {
console.error(err)
}
})
}, [])

const matchRoute = useStableCallback<MatchRouteFn<TRouteTree>>(
(location, opts) => {
location = {
Expand Down Expand Up @@ -1429,6 +1432,60 @@ export function RouterProvider<
},
)

const injectedHtmlRef = React.useRef<InjectedHtmlEntry[]>([])

const injectHtml = useStableCallback(
async (html: string | (() => Promise<string> | string)) => {
injectedHtmlRef.current.push(html)
},
)

const dehydrateData = useStableCallback(
<T,>(key: any, getData: T | (() => Promise<T> | T)) => {
if (typeof document === 'undefined') {
const strKey = typeof key === 'string' ? key : JSON.stringify(key)

injectHtml(async () => {
const id = `__TSR_DEHYDRATED__${strKey}`
const data =
typeof getData === 'function' ? await (getData as any)() : getData
return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
strKey,
)}"] = ${JSON.stringify(data)}
;(() => {
var el = document.getElementById('${id}')
el.parentElement.removeChild(el)
})()
</script>`
})

return () => hydrateData<T>(key)
}

return () => undefined
},
)

const hydrateData = useStableCallback(<T extends any = unknown>(key: any) => {
if (typeof document !== 'undefined') {
const strKey = typeof key === 'string' ? key : JSON.stringify(key)

return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
}

return undefined
})

React.useLayoutEffect(() => {
startReactTransition(() => {
try {
load()
} catch (err) {
console.error(err)
}
})
}, [])

const routerContextValue: RouterContext<TRouteTree> = {
routeTree: router.routeTree,
navigate,
Expand All @@ -1442,6 +1499,10 @@ export function RouterProvider<
buildLocation,
subscribe: router.subscribe,
resetNextScrollRef,
injectedHtmlRef,
injectHtml,
dehydrateData,
hydrateData,
}

return (
Expand Down
80 changes: 40 additions & 40 deletions packages/react-router/src/awaited.tsx
Original file line number Diff line number Diff line change
@@ -1,40 +1,40 @@
// import { DeferredPromise, isDehydratedDeferred } from '@tanstack/react-router'
// import { useRouter } from './react'

// export type AwaitOptions<T> = {
// promise: DeferredPromise<T>
// }

// export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
// const router = useRouter()

// let state = promise.__deferredState
// const key = `__TSR__DEFERRED__${state.uid}`

// if (isDehydratedDeferred(promise)) {
// state = router.hydrateData(key)!
// promise = Promise.resolve(state.data) as DeferredPromise<any>
// promise.__deferredState = state
// }

// if (state.status === 'pending') {
// throw promise
// }

// if (state.status === 'error') {
// throw state.error
// }

// router.dehydrateData(key, state)

// return [state.data]
// }

// export function Await<T>(
// props: AwaitOptions<T> & {
// children: (result: T) => JSX.Element
// },
// ) {
// const awaited = useAwaited(props)
// return props.children(...awaited)
// }
import { useRouter } from './RouterProvider'
import { DeferredPromise, isDehydratedDeferred } from './defer'

export type AwaitOptions<T> = {
promise: DeferredPromise<T>
}

export function useAwaited<T>({ promise }: AwaitOptions<T>): [T] {
const router = useRouter()

let state = promise.__deferredState
const key = `__TSR__DEFERRED__${state.uid}`

if (isDehydratedDeferred(promise)) {
state = router.hydrateData(key)!
promise = Promise.resolve(state.data) as DeferredPromise<any>
promise.__deferredState = state
}

if (state.status === 'pending') {
throw promise
}

if (state.status === 'error') {
throw state.error
}

router.dehydrateData(key, state)

return [state.data]
}

export function Await<T>(
props: AwaitOptions<T> & {
children: (result: T) => JSX.Element
},
) {
const awaited = useAwaited(props)
return props.children(...awaited)
}
Loading

0 comments on commit 76f411c

Please sign in to comment.