SolidStart layout shifts using createAsync

Hi, I found out that I experience layout shifts whenever a createAsync is re-triggered. For example here:
export default function ActivitySubmissions() {
const [params, setParams] = createStore<ActivitySubmissionsParams>(
structuredClone(defaultParams),
);

const data = createAsync(() => getActivitySubmissions({ ...params }));

const pagination = () => data()?.pagination;
const submissions = () => data()?.submissions;

const setPage = (page: number) => {
setParams('page', page);
};

return (
<main class="mx-auto flex flex-col gap-8 py-8 sm:w-11/12">
<TopBar />
<div class="rounded-md bg-red-300 shadow-md min-h-[1000px]">
{/* <Suspense fallback={<div>Loading...</div>}>
<ActivitySubmissionsTable submissions={submissions()} />
</Suspense> */}
</div>

<div class="flex justify-center ">
<Pagination ...>
<!-- Omitted -->
</Pagination>
</div>
</main>
);
}
export default function ActivitySubmissions() {
const [params, setParams] = createStore<ActivitySubmissionsParams>(
structuredClone(defaultParams),
);

const data = createAsync(() => getActivitySubmissions({ ...params }));

const pagination = () => data()?.pagination;
const submissions = () => data()?.submissions;

const setPage = (page: number) => {
setParams('page', page);
};

return (
<main class="mx-auto flex flex-col gap-8 py-8 sm:w-11/12">
<TopBar />
<div class="rounded-md bg-red-300 shadow-md min-h-[1000px]">
{/* <Suspense fallback={<div>Loading...</div>}>
<ActivitySubmissionsTable submissions={submissions()} />
</Suspense> */}
</div>

<div class="flex justify-center ">
<Pagination ...>
<!-- Omitted -->
</Pagination>
</div>
</main>
);
}
I have a "middle div" colored in red here. Whenever I change one of the params (such as page or search filters), the createAsync is retriggered. But while it does that the middle part (in red) disappears for a split second and the page is re-scrolled back up. This makes using the pagination very obnoxious. Has anyone ever experienced that ? I don't understand why in this case it is needed for Solid to re-render the whole div each time.
21 Replies
Brendonovich
Brendonovich•2w ago
That's probably because the Suspense fallback gets shown while the createAsync refetches You can either wrap the setParams call with startTransition or use the latest field of data instead of calling it
Cyber Grandma
Cyber GrandmaOP•2w ago
use the latest field of data instead of calling it
Thank you a lot ;) Im wondering also if I should at all use a Suspense to show loading since the page won't be shown before it has finished loading the createAsync server side
Brendonovich
Brendonovich•2w ago
That would only apply for the first load - if you navigate there from another page then the suspense may be shown I'd recommend keeping the Suspense
Cyber Grandma
Cyber GrandmaOP•2w ago
Nah, it just wait before it has finished loading on my side
Brendonovich
Brendonovich•2w ago
Ah if you don't have a suspense then that might happen
Cyber Grandma
Cyber GrandmaOP•2w ago
The suspense is still there
Brendonovich
Brendonovich•2w ago
I don't see one around Pagination so that might be it
Cyber Grandma
Cyber GrandmaOP•2w ago
I only changed this :
ts
const pagination = () => data.latest?.pagination;
const submissions = () => data.latest?.submissions;
ts
const pagination = () => data.latest?.pagination;
const submissions = () => data.latest?.submissions;
Following your recommendations
Brendonovich
Brendonovich•2w ago
If the Pagination component is calling pagination() then that's also a place that can suspend Since it accesses data
Cyber Grandma
Cyber GrandmaOP•2w ago
For my pagination I use those:
<Pagination
class="*:join *:inline-flex"
count={pagination()?.totalPages ?? 10}
page={pagination()?.page ?? 1}
onPageChange={setPage}
siblingCount={2}
itemComponent={(props) => (
<Pagination.Item
class="join-item btn aria-[current=page]:btn-active"
page={props.page}
>
{props.page}
</Pagination.Item>
)}
ellipsisComponent={() => (
<Pagination.Ellipsis class="join-item btn btn-disabled">
...
</Pagination.Ellipsis>
)}
>
<Pagination.Previous class="join-item btn">
<RiArrowsArrowLeftDoubleFill size={18} />
</Pagination.Previous>
<Pagination.Items />
<Pagination.Next class="join-item btn">
<RiArrowsArrowRightDoubleFill size={18} />
</Pagination.Next>
</Pagination>
<Pagination
class="*:join *:inline-flex"
count={pagination()?.totalPages ?? 10}
page={pagination()?.page ?? 1}
onPageChange={setPage}
siblingCount={2}
itemComponent={(props) => (
<Pagination.Item
class="join-item btn aria-[current=page]:btn-active"
page={props.page}
>
{props.page}
</Pagination.Item>
)}
ellipsisComponent={() => (
<Pagination.Ellipsis class="join-item btn btn-disabled">
...
</Pagination.Ellipsis>
)}
>
<Pagination.Previous class="join-item btn">
<RiArrowsArrowLeftDoubleFill size={18} />
</Pagination.Previous>
<Pagination.Items />
<Pagination.Next class="join-item btn">
<RiArrowsArrowRightDoubleFill size={18} />
</Pagination.Next>
</Pagination>
Brendonovich
Brendonovich•2w ago
Add a Suspense around main and the navigation from another page should be instant instead of waiting
Cyber Grandma
Cyber GrandmaOP•2w ago
Mmh I see That did work, nice
Brendonovich
Brendonovich•2w ago
The idea is that navigation happens inside a Transition, and since an existing Suspense somewhere higher up in your app is being triggered by accessing data, the Transition pauses the navigation until the Suspense resolves
Cyber Grandma
Cyber GrandmaOP•2w ago
Yeah maybe I'll add a big suspense above everything, even with multiple asyncs I guess its better than having the layout load in multiple steps
Brendonovich
Brendonovich•2w ago
Yeah it's entirely up to you, you can add and remove Suspense in different places to achieve different styles of loading states When you add the Suspense around main the data access is then caught by that brand new Suspense that doesn't have any previous UI to show, rather than an existing Suspense that can keep showing the route you're navigating from So it navigates instantly and you see the suspense fallback
Cyber Grandma
Cyber GrandmaOP•2w ago
🫡 I'm going out of scope but shouldn't the loading be instant since I use cache ?
export const getActivitySubmissions = cache(
async (params: ActivitySubmissionsParams) => {
'use server';

.....

await Bun.sleep(1000);

return {
...
};
},
'activity-submissions',
);
export const getActivitySubmissions = cache(
async (params: ActivitySubmissionsParams) => {
'use server';

.....

await Bun.sleep(1000);

return {
...
};
},
'activity-submissions',
);
I have added a Bun.sleep here just to see the loading suspense, but shouldn't it just skip thins function alltogether ?
Brendonovich
Brendonovich•2w ago
No? It's still gotta run the function - cache is per-request, not a global cache
Cyber Grandma
Cyber GrandmaOP•2w ago
Oh I thought so Anyway it seems like its deprecated but query should work the same I guess
Brendonovich
Brendonovich•2w ago
Ye same thing different name
Cyber Grandma
Cyber GrandmaOP•2w ago
So it would only help if I called the function multiple times during the same request, like a parent and child component both calling it ? If I understand
Brendonovich
Brendonovich•2w ago
Yeah
Want results from more Discord servers?
Add your server