Replies: 2 comments
-
If instead I do this: import {createRoute, createRootRouteWithContext, lazyRouteComponent, createRouter} from '@tanstack/react-router'
import {HalfLayout} from './components/layout.tsx'
import {EmptyObject} from '../shared/util-types.ts'
import {Spinner} from './components/spinner.tsx'
const rootRoute = createRootRouteWithContext<EmptyObject>()({
})
const indexRoute = createRoute({
getParentRoute: () => rootRoute,
path: "/",
component: lazyRouteComponent(() => import('./pages/index')),
})
const aboutRoute = createRoute({
getParentRoute: () => rootRoute,
path: "about",
// component: createLayout("About", () => import('./pages/about')),
component: lazyRouteComponent(() => import('./pages/about')),
})
const routeTree = rootRoute.addChildren([
indexRoute,
aboutRoute,
])
export const router = createRouter({
routeTree,
defaultPreload: 'intent',
defaultPreloadStaleTime: 999_999,
defaultPreloadGcTime: 999_999,
defaultStaleTime: 500,
defaultPendingComponent: Spinner,
defaultPendingMs: 1,
defaultComponent: HalfLayout,
defaultPendingMinMs: 1500,
}) Which I think is maybe closer to the way Tanstack wants me to do it, the behavior is even more awkward. If I refresh the page, first it shows the |
Beta Was this translation helpful? Give feedback.
-
There's a whole multitude of issues going on here, and then out-of-the-box behavior is really suboptimal IMO.
export enum PromiseStatus {
PENDING = 'pending',
FULFILLED = 'fulfilled',
REJECTED = 'rejected',
}
export class PromiseMonitor<V,E=unknown> {
private readonly _promise: Promise<V>
private _status = PromiseStatus.PENDING
private _value: V|undefined
private _reason: E|undefined
constructor(promise: Promise<V>) {
this._promise = promise.then(value => {
this._status = PromiseStatus.FULFILLED
this._value = value
return Promise.resolve(value)
}, reason => {
this._status = PromiseStatus.REJECTED
this._reason = reason
return Promise.reject(reason)
})
}
get status() {
return this._status
}
get value() {
return this._value
}
get reason() {
return this._reason
}
get promise() {
return this._promise
}
}
function eager(load: () => PromiseMonitor<{ default: ComponentType }>): ComponentType {
const monitor = load()
return monitor.status === PromiseStatus.FULFILLED
? monitor.value!.default
: lazy(() => monitor.promise)
} Then you give it
function createLoaderLayout(title: string, importer: () => Promise<{ default: ComponentType }>, preload = false) {
let loadPromise: PromiseMonitor<{ default: ComponentType }>
const load = () => {
loadPromise ??= new PromiseMonitor(importer())
return loadPromise
}
if(preload) requestIdleCallback(load)
return {
pendingComponent: createLoader(title),
loader: load,
component: createLayout(title, load),
}
}
const indexRoute = createRoute({
getParentRoute,
path: "/",
...createLoaderLayout("Index", () => import('./pages/index'), true),
}) Wherein my loader and layout are nearly the same: function createLoader(title: string): RouteComponent {
return () => {
return (
<Layout title={title}>
<Spinner />
</Layout>
)
}
}
function createLayout(title: string, load: () => PromiseMonitor<{ default: ComponentType }>): RouteComponent {
return () => {
const Content = eager(load)
return (
<Layout title={title}>
<Content />
</Layout>
)
}
} But I can't figure out how to merge them and get the behavior I want. And this is all before I've mixed in any data, which will throw another wrench in here. I'm also not sure why the return value from |
Beta Was this translation helpful? Give feedback.
-
I have a page that looks like this:
I want to code-split the "About" page. When you hover over "About" it should start preloading the JS and whatever else for that page. When you click About but before the preloading is done, it should instantly update the page title and show a loading state like this:
I've nearly got this working, but I'm noticing some weird behavior with how often
preload
is invoked, and I'm not sure if it's intentional or not. Looks like a bug to me.First, my code looks like this:
When I hover over "About" I see "PRELOADING About" in my Chrome dev tools console, indicating it's running
comp.preload
like expected/desired. And if I put it on "Slow 3G" I can see my loading state, as desired.What I think is buggy, is if I rapidly click "About", it runs
preload
every 500ms. So that tells me it's waiting for thedefaultStaleTime
-- and I totally expect it to rerender my component after that period of time, but I don't know why it's rerunningpreload
. There's no preloading necessary, it's already been loaded and I havedefaultPreloadStaleTime
set to999_999
so the preload should not be stale.Furthermore, after navigating (e.g. click "Index") if I mouse over "About" it reruns the About preload again. Why? Again, the cache shouldn't be stale.
There's probably a more idiomatic way to do this, but I can't figure it out. I found
lazyRouteComponent
from the kitchen sync but that doesn't seem to have the semantics I want. When I click around the kitchen sync, it looks like it just hangs for a second for the JS to load and then it navigates, and then it pops in a spinner for the data only. I want a spinner inside the layout for the JS itself. My above code is the closest I could figure to doing that.Also the
preload
itself is...interesting.preload
wants you to return aPromise
presumably so that Tanstack Router can wait for the preload to finish before navigating, which almost kinda makes sense if you want the wait-then-navigate behavior, but I want instant navigation so I'm ignoring theloader()
result and I just returnPromise.resolve()
immediately, and then if it needs to wait even longer after the user clicks, that's whatconst Content = lazy(loader)
is for.And if wasn't obvious, my "spinner" is inside the
Layout
itself:Beta Was this translation helpful? Give feedback.
All reactions