Understanding Suspense in SolidStart SSR

To understand how Suspense behave in a SolidStart SSR project, I have project created using the SolidStart bare template, and updated the Home page and About page as in the attachment 2 questions: 1) When I first visit the page http://localhost:3000/, it does not show fallback of Suspense. I understand this as SSR will resolve all Suspense boundaries before returning the page. However, when I click on the about link to move to http://localhost:3000/about, it show the about page immediately, with fallback of Suspense, when the values are resolved, it shows the resolved values. How can I make the website wait for createServerData$ to resolve successfully first before showing the page (just like on the first page load)? 2) I add a button to trigger refetchRouteData. However, when I click on the button for createServerData$ and createRouteData , their Suspense don't show fallback. I thought the serverData and clientData are Resource and so, I can show the loading state with serverData.loading, just like how compData behaves, but this is not the case. How can I make them show the loading state? For 2), the real world use case would be a page showing a list of items, with pagination. When I click on the pagination, it refetches the route data and should show loading icon while re-fetching
1 Reply
anhvu0911
anhvu091113mo ago
For 2), Looking at the source code of refetchRouteData https://github.com/solidjs/solid-start/blob/8cfe543c18c2ee2aed30478da9893c37ec8be7f7/packages/start/data/createRouteData.tsx#L139 It uses startTransition under the hood, so it will not trigger callback of suspense. If I modify the code to not use startTransition, then the fallback is displayed as expected
export function refetchRouteData(key?: string | any[] | void) {
if (isServer) throw new Error("Cannot refetch route data on the server.");

for (let refetch of resources) refetch(key);
}
export function refetchRouteData(key?: string | any[] | void) {
if (isServer) throw new Error("Cannot refetch route data on the server.");

for (let refetch of resources) refetch(key);
}
It seems that the code is introduced in https://github.com/solidjs/solid-start/issues/304 The question is how can we get the pending state, while not triggering Suspense? One workaround is to useTransition. We startTransition on calling refetchRouteData, and get the pending state.
import { Suspense, useTransition } from "solid-js"
import { A, Title, refetchRouteData, useRouteData } from "solid-start"
import { createServerData$ } from "solid-start/server"
import { sleep } from "~/utils/async"

export function sleep(timeout = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, timeout)
})
}

export function routeData() {
const serverData = createServerData$(
async () => {
await sleep(2000)
return "serverData ok"
},
{ key: "serverData_ABOUT" }
)

return { serverData }
}

export default function About() {
const { serverData } = useRouteData()
const [pending, startTransition] = useTransition()

return (
<main>
<Title>About</Title>
<h1>About!</h1>
<p>
<A href="/">HOME</A>
<br />
<A href="/about">ABOUT</A>
</p>

<Suspense fallback="loading serverData()...">
<div>
pending: {pending() ? "true" : "false"}
<br />
serverData: {serverData()}
<button onClick={() => startTransition(() => refetchRouteData("serverData_ABOUT"))}>
Refetch serverData
</button>
</div>
</Suspense>
</main>
)
}
import { Suspense, useTransition } from "solid-js"
import { A, Title, refetchRouteData, useRouteData } from "solid-start"
import { createServerData$ } from "solid-start/server"
import { sleep } from "~/utils/async"

export function sleep(timeout = 1000) {
return new Promise((resolve) => {
setTimeout(resolve, timeout)
})
}

export function routeData() {
const serverData = createServerData$(
async () => {
await sleep(2000)
return "serverData ok"
},
{ key: "serverData_ABOUT" }
)

return { serverData }
}

export default function About() {
const { serverData } = useRouteData()
const [pending, startTransition] = useTransition()

return (
<main>
<Title>About</Title>
<h1>About!</h1>
<p>
<A href="/">HOME</A>
<br />
<A href="/about">ABOUT</A>
</p>

<Suspense fallback="loading serverData()...">
<div>
pending: {pending() ? "true" : "false"}
<br />
serverData: {serverData()}
<button onClick={() => startTransition(() => refetchRouteData("serverData_ABOUT"))}>
Refetch serverData
</button>
</div>
</Suspense>
</main>
)
}