S
SolidJS3mo ago
ry

Resource loading from param blocks navigation

https://stackblitz.com/edit/github-xdufjx?file=src%2Froutes%2Fdetail%2F%5Bid%5D%2Findex.tsx on latest solidstart
export default function DetailPage() {
const params = useParams();
const [data] = createResource(
() => params.id,
(id) => getData(id)
);
return (
<main>
<h1>Detail /{params.id}</h1>

<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</main>
);
}
export default function DetailPage() {
const params = useParams();
const [data] = createResource(
() => params.id,
(id) => getData(id)
);
return (
<main>
<h1>Detail /{params.id}</h1>

<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</main>
);
}
When navigating to /details/a from /, suspense fallback shows. After data loads, now navigate to detail b and see how the page freezes. You can't go to home during this. Shouldn't the suspense show and not block everything when going a -> b? Not sure if i'm doing something wrong here.
10 Replies
peerreynders
peerreynders3mo ago
1. Don't use createResource with cache. That's what createAsync is for.
import { createAsync, useParams } from '@solidjs/router';
import { Suspense } from 'solid-js';
import { getData } from '~/API';

export default function DetailPage() {
const params = useParams();
const data = createAsync(() => getData(params.id));
return (
<main>
<h1>Detail /{params.id}</h1>

<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</main>
);
}
import { createAsync, useParams } from '@solidjs/router';
import { Suspense } from 'solid-js';
import { getData } from '~/API';

export default function DetailPage() {
const params = useParams();
const data = createAsync(() => getData(params.id));
return (
<main>
<h1>Detail /{params.id}</h1>

<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</main>
);
}
2. cache implicitly triggers a transition. Consequently rendering happens offscreen and the page isn't swapped in until all async sources have settled. Compare to: https://www.solidjs.com/tutorial/async_transitions?solved Using smart cache opts into the route loading the data (rather than through components) so for a nested route update the screen doesn't change until the route render is ready. The rendering of fallback child content while any async sources have not yet settled is more aligned with the philosophy of component data loading. 3. Now the odd bit: plain createResource (bypassing cache) should let you opt out of implicit transitions and load component data after routing. https://playground.solidjs.com/anonymous/dcf477cc-c879-4c8d-81f0-21bc377633eb However right now in the context of SolidStart createResource also triggers transitions and I don't know why. https://stackblitz.com/edit/github-xdufjx-hxnbnc?file=src%2Froutes%2Fdetail%2F[id].tsx
SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
Solid Playground
Quickly discover what the solid compiler will generate from your JSX template
StackBlitz
Solid-start With Tailwindcss Example (forked) - StackBlitz
Run official live example code for Solid-start With Tailwindcss, created by Solidjs on StackBlitz
ry
ry3mo ago
Thanks for this. Yeah basically I want to opt out of the transitions because if the page has multiple resources, I don't want 1 slow resource to pause the navigation. Would you say this is a bug that createResource does this? https://stackblitz.com/edit/github-xdufjx-uf411k?file=src%2FAPI.ts
peerreynders
peerreynders3mo ago
Would you say this is a bug that createResource does this?
I can't be sure. AFAIK createResource is slated to be replaced by createAsync by SolidJS 2.0 and I was under the impression that implicit transitions are supposed to be part of solid-router but if createResource is now already being integrated in this fashion then I'm missing what the transition escape hatch would be. There has been an open issue since January https://github.com/solidjs/solid-router/issues/352 but it pertains to createAsync—as there is no response, I'm assuming it's being treated as a documentation issue about opting into implicit transitions; solid-router is trying to emulate browser routing behaviour and avoid screen tearing. https://twitter.com/jaffathecake/status/1529370639915687936 I also observed some things that made me wonder whether I was starting to see things. Transitions exist to enable alternate branch rendering while paint holding but
export default function DetailPage(props: RouteSectionProps) {
const [data] = createResource(() => props.params.id, getDC);
const [transPending] = useTransition();
createEffect(
()=> console.log(`ID: ${props.params.id} trans:${transPending() ? 'started' : 'none'}`)
);
return (
<>
<h1>Detail /{props.params.id}</h1>
<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</>
);
}
export default function DetailPage(props: RouteSectionProps) {
const [data] = createResource(() => props.params.id, getDC);
const [transPending] = useTransition();
createEffect(
()=> console.log(`ID: ${props.params.id} trans:${transPending() ? 'started' : 'none'}`)
);
return (
<>
<h1>Detail /{props.params.id}</h1>
<Suspense fallback={<div>...</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</>
);
}
props.params.id inside createResource updates immediately on navigation but inside of createEffect (and by extension render effect) it only updates once the transition has completed - so it almost looks like transitions also delay change propagation on effect boundaries (i.e. not only paint-holding but also value-holding). At this point I feel like I have to look at a fresh project to see whether this behaviour is consistent.
GitHub
not properly triggering nested Suspense · Issue #352 · solidjs/sol...
Describe the bug When using A components for navigation to pages which rely on params or location to render, it is not properly triggering nested Suspense fallbacks. It instead waits until the enti...
Jake Archibald (@jaffathecake) on X
@BigupJeff In normal browser navigations, the URL only changes when the old content becomes inert. That only happens when it has a response for the new content. Try it with some network throttling 😀
Twitter
peerreynders
peerreynders3mo ago
So it may be a bug—another explanation is that solid-router is moving away from using fallbacks available in core but will instead force you to pro-actively set up an “optimistic view” before entering a transition.
ry
ry3mo ago
That workaround in the issue does seem like a viable escape hatch that makes sense since you exclude the resource from the navigation transition. Although it might not be obvious to figure out. Might just need some more documentation around it. I believe createAsync is just a higher-level wrapper around createResource so the transitions work the same.
// Navigation no longer waits for resource
<Show when={!isRouting()}>
<Suspense fallback={<div>...</div>}>
<div>{fdata()}</div>
</Suspense>
</Show>
// Navigation no longer waits for resource
<Show when={!isRouting()}>
<Suspense fallback={<div>...</div>}>
<div>{fdata()}</div>
</Suspense>
</Show>
I experience the same behavior you describe for the effect param value holding. To me this seems intentional? Since you want to hold the same state during the transition.
peerreynders
peerreynders3mo ago
Since you want to hold the same state during the transition.
In principle I agree but effectively it defeats any workarounds. What seems to be happening is that the alternate rendering branch is limited to the DOM subtree under the closest suspense boundary while the value-holding applies to the entire page—which makes sense if you are trying to emulate browser behaviour, keeping the stale page stable until the new page is ready to render. That said, value-holding will lead to screen tearing for anyone trying to get fallback+optimistic values to work. Based on your example I tried this:
import type { RouteSectionProps } from '@solidjs/router';

export default function DetailPage(props: RouteSectionProps) {
const [data] = createResource(() => props.params.id, getDC);
const [isPending] = useTransition();
return (
<>
<h1>Detail /{props.params.id}</h1>
<Show when={!isPending()} fallback={<div>...show</div>}>
<Suspense fallback={<div>...suspense</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</Show>
</>
);
}
import type { RouteSectionProps } from '@solidjs/router';

export default function DetailPage(props: RouteSectionProps) {
const [data] = createResource(() => props.params.id, getDC);
const [isPending] = useTransition();
return (
<>
<h1>Detail /{props.params.id}</h1>
<Show when={!isPending()} fallback={<div>...show</div>}>
<Suspense fallback={<div>...suspense</div>}>
<div>{data()?.join(' ')}</div>
</Suspense>
</Show>
</>
);
}
This will render the fallback once the transition starts but the <h1> will display the stale value (which is consistent with the URL in the address bar which doesn't change until the transition ends). So from the user perspective there is an indication that something is happening but there is no obvious way to provide the optimistic value to the <h1> which is what the typical developer intent would be. FYI: I found useTransition to be more reliable/useful than useIsRouting in this example. So the broader message seems to be: stop relying on component level fallbacks. Given that the page doesn't have access to the browser's reload button appearance, it needs to include exactly one element to indicate background/busy activity which at the very least triggers when a transition is pending.
peerreynders
peerreynders3mo ago
Interestingly the React RSC demo used a spinner inside the search field which in my port I drove with useIsRouting
https://github.com/peerreynders/solid-start-notes-basic/blob/ea9ed12a4b642e5295537f49b2613d460c21e6be/src/components/search-field.tsx#L47 Compare that to the solid-start example: https://github.com/solidjs/solid-start/blob/2d75d5fedfd11f739b03ca34decf23865868ac09/examples/notes/src/components/SearchField.tsx#L30 Bingo—it uses useTransition. I take this as evidence that the current createResource behaviour is working as intended. It's trying to wean you off component level fallbacks and drive you towards one single page level busy indicator.
GitHub
solid-start-notes-basic/src/components/search-field.tsx at ea9ed12a...
Basic client rendered notes app using SolidStart beta - peerreynders/solid-start-notes-basic
GitHub
solid-start/examples/notes/src/components/SearchField.tsx at 2d75d5...
SolidStart, the Solid app framework. Contribute to solidjs/solid-start development by creating an account on GitHub.
ry
ry3mo ago
I landed on a similar solution by dimming the page and a loader in the navbar in my app to indicate this to the user. I guess useTransition would be more correct I still think having component level fallbacks make sense in some cases like the one I show, with multiple resources where 1 is slow, relying only on a page level indicater seems like bad ux. Similar to how you can add deferStream to indicate which resources need to load before flushing the page.
peerreynders
peerreynders3mo ago
relying only on a page level indicater seems like bad ux.
See Nielsen Norman Group: Skeleton Screens 101 and anecdotally: https://twitter.com/malchata/status/1456649407760408576 To answer your original question solid-router is deliberately guiding you towards this approach: https://stackblitz.com/edit/github-xdufjx-hxnbnc?file=src%2Fcomponents%2Fglobal-loader.tsx,src%2Froutes%2Fdetail%2F[id].tsx
malchata.htm (@malchata) on X
what is it with financial institution websites and the cavalcade of like fifteen loading spinners
Twitter
StackBlitz
Using a global loader for transitions - StackBlitz
solid-router's implicit transitions discourage page partial loading fallbacks in favour of paint-holding the stale page while showing one central loading indicator.
Nielsen Norman Group
Skeleton Screens 101
A skeleton screen is used as a placeholder while users wait for a page to load. This progress indicator is used for full page loads and reduces the perception of a long loading time by providing clues for how the page will ultimately look.
ry
ry3mo ago
Yeah I see where you are coming from, totally agree that many sites have gone too far with loaders. But take youtube for example, navigating to a new video has similar transition hold for the main data with a page loader, but it no longer holds waiting for comments to load. I think waiting for secondary data to load before showing a page is worse.