Show Loading

I'm trying to figure out how to show my loading spinner when the todo is streaming from the server:
import { createAsync, query, RouteDefinition } from "@solidjs/router";
import { ErrorBoundary, Show } from "solid-js";
import { Loading } from "./loading";

type Todo = {
title: string
};

const getTodo = query(async () => {
'use server';
const randomTodo = Math.floor(Math.random() * 200) + 1;
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos/${randomTodo}`);
return await todo.json() as Todo;
}, 'todo');

export const route = {
preload: () => getTodo()
} satisfies RouteDefinition;


export default function Home() {
const todo = createAsync(() => getTodo(), { deferStream: true });

return (
<main class="flex flex-col justify-center items-center mt-5 gap-3">
<h1 class="text-2xl">Todo</h1>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Show when={todo()} fallback={<Loading />}>
{(data) => (
<h2>{data().title}</h2>
)}
</Show>
</ErrorBoundary>
</main>
);
}
import { createAsync, query, RouteDefinition } from "@solidjs/router";
import { ErrorBoundary, Show } from "solid-js";
import { Loading } from "./loading";

type Todo = {
title: string
};

const getTodo = query(async () => {
'use server';
const randomTodo = Math.floor(Math.random() * 200) + 1;
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos/${randomTodo}`);
return await todo.json() as Todo;
}, 'todo');

export const route = {
preload: () => getTodo()
} satisfies RouteDefinition;


export default function Home() {
const todo = createAsync(() => getTodo(), { deferStream: true });

return (
<main class="flex flex-col justify-center items-center mt-5 gap-3">
<h1 class="text-2xl">Todo</h1>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Show when={todo()} fallback={<Loading />}>
{(data) => (
<h2>{data().title}</h2>
)}
</Show>
</ErrorBoundary>
</main>
);
}
Currently, it never loads. J
6 Replies
mdynnl
mdynnl5d ago
Yeah, because of deferStream: true ssr will wait for that async to resolve first if you disable client side js and check the response, you should see that async signal right in the html in seroval encoded form
peerreynders
peerreynders5d ago
Currently, it never loads.
Even later during CSR the getTodo will suspend all the way up to the Suspense boundary in the app.tsx:
export default function App() {
return (
<Router
root={(props) => (
<MetaProvider>
<Title>SolidStart - Basic</Title>
<a href="/">Index</a>
<a href="/about">About</a>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}
export default function App() {
return (
<Router
root={(props) => (
<MetaProvider>
<Title>SolidStart - Basic</Title>
<a href="/">Index</a>
<a href="/about">About</a>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}
Try this instead:
<Suspense fallback={<Loading />}>
<Show when={todo()}>{(data) => <h2>{data().title}</h2>}</Show>
</Suspense>
<Suspense fallback={<Loading />}>
<Show when={todo()}>{(data) => <h2>{data().title}</h2>}</Show>
</Suspense>
jdgamble555
jdgamble555OP5d ago
If I move the Suspense to the child component, I get a Hydration Mismatch... however, if I have two Suspense it works.
Is it correct usage to have two <Suspense> tags?
// app.tsx
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";

export default function App() {
return (
<Router
root={props => (
<MetaProvider>
<Title>SolidStart - Basic</Title>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}

// index.tsx
import { createAsync, query, RouteDefinition } from "@solidjs/router";
import { ErrorBoundary, Show, Suspense } from "solid-js";
import { Loading } from "./loading";

type Todo = {
title: string
};

const getTodo = query(async () => {
'use server';
const randomTodo = Math.floor(Math.random() * 200) + 1;
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos/${randomTodo}`);
return await todo.json() as Todo;
}, 'todo');

export const route = {
preload: () => getTodo()
} satisfies RouteDefinition;


export default function Home() {
const todo = createAsync(() => getTodo());

return (
<main class="flex flex-col justify-center items-center mt-5 gap-3">
<h1 class="text-2xl">Todo</h1>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Suspense fallback={<Loading />}>
<Show when={todo()}>
{(data) => (
<h2>{data().title}</h2>
)}
</Show>
</Suspense>
</ErrorBoundary>
</main>
);
}
// app.tsx
import { MetaProvider, Title } from "@solidjs/meta";
import { Router } from "@solidjs/router";
import { FileRoutes } from "@solidjs/start/router";
import { Suspense } from "solid-js";
import "./app.css";

export default function App() {
return (
<Router
root={props => (
<MetaProvider>
<Title>SolidStart - Basic</Title>
<Suspense>{props.children}</Suspense>
</MetaProvider>
)}
>
<FileRoutes />
</Router>
);
}

// index.tsx
import { createAsync, query, RouteDefinition } from "@solidjs/router";
import { ErrorBoundary, Show, Suspense } from "solid-js";
import { Loading } from "./loading";

type Todo = {
title: string
};

const getTodo = query(async () => {
'use server';
const randomTodo = Math.floor(Math.random() * 200) + 1;
const todo = await fetch(`https://jsonplaceholder.typicode.com/todos/${randomTodo}`);
return await todo.json() as Todo;
}, 'todo');

export const route = {
preload: () => getTodo()
} satisfies RouteDefinition;


export default function Home() {
const todo = createAsync(() => getTodo());

return (
<main class="flex flex-col justify-center items-center mt-5 gap-3">
<h1 class="text-2xl">Todo</h1>
<ErrorBoundary fallback={<div>Something went wrong!</div>}>
<Suspense fallback={<Loading />}>
<Show when={todo()}>
{(data) => (
<h2>{data().title}</h2>
)}
</Show>
</Suspense>
</ErrorBoundary>
</main>
);
}
I'm thinking this is not correct, but it works? J
peerreynders
peerreynders5d ago
Is it correct usage to have two <Suspense> tags?
You can have as many as you need. Suspense simply demarcates a section of the UI with impending changes due to asynchronous operations it depends on: - and therefore shows its fallback or - holds its stale content if it is part of the current transition, delaying visually updating until all of the asynchronous operations under the suspense boundary have settled.
jdgamble555
jdgamble555OP5d ago
great thanks!
peerreynders
peerreynders5d ago
If I move the Suspense to the child component, I get a Hydration Mismatch…
I suspect that you need the top level suspense boundary for SSR to work reliably. When there are asynchronous operations within the component tree that aren't under a suspense boundary undesirable things tend to happen during SSR. Imagine the unsettled promise of a resource/async being thrown towards the root, shredding through everything on its way and not being caught by a suspense boundary that can try again once all the promises under it have settled (perhaps not technically accurate but metaphorically a good image).

Did you find this page helpful?