Skip to content

[Bug?]: Transition makes it impossible to show loading screen for long-running backend processes #1870

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
danieltroger opened this issue Mar 24, 2025 · 2 comments
Labels
bug Something isn't working needs triage

Comments

@danieltroger
Copy link
Contributor

danieltroger commented Mar 24, 2025

Duplicates

  • I have searched the existing issues

Latest version

  • I have tested the latest version

Context 🔦

I have a very long-running process on the backend (it involves starting up a browser at an arbitrary URL and collecting some data from the page, then asking an LLM about it) which takes roughly 10-30s.

I would like to keep using the super-simple backend/frontend integration that solid start provides (which blew my mind). Right now it works great in the initial pageload, however, when the user changes their input, solid's router inadvertently starts a transition around my app, which leads to my loading screen not being displayed.

A workaround is to also displaying my loading screen when useIsRouting() is true, but my loading screen shows the user exactly what steps is in progress and therefore I need to be able to know which createAsync/createResource is loading.

Due to the cursed transition this is impossible. I can't use individual <Suspense>s nor can I use .loading of a resource, as everything is lying during a transition.

Steps to reproduce 🕹

Steps:

  1. Go to https://stackblitz.com/edit/github-3j8b6an5-tc2z1ort?file=src%2Froutes%2Findex.tsx using a chromium based browser
  2. Wait for "We are loading…" to complete
  3. Enter something into the input field and press submit

Current behavior 😯

After the user presses "submit", NOTHING happens until the result is displayed after 10s.

Expected behavior 🤔

"We are loading…" is displayed again until the next response shows up


Yet unanswered thread in discord: https://discord.com/channels/722131463138705510/910635844119982080/1353843298019835986

@danieltroger danieltroger added bug Something isn't working needs triage labels Mar 24, 2025
@danieltroger
Copy link
Contributor Author

danieltroger commented Mar 25, 2025

Question was answered on discord, pasting answers for SEO of future people having this issue:

𖠰🌲 — Yesterday at 21:46
How can I stop latest solid-start's router from "transitioning" when I change a query param?
Right now it freezes my whole app from updating. Even if I nest a Suspense inside my route around everything, that suspense fallback isn't shown.
My backend takes 20 seconds to respond and I NEED to show a loading screen but right now the built in transitioning that seems unbreakable stops me from doing that
I've had issues with transitions since 2023 ⁠reactivity⁠
⁠reactivity⁠
Also, something like playgorund.solidjs.com but for solid start would be nice to share reproductions
Jasmin — Yesterday at 21:54
Stackblitz is a good way https://stackblitz.com/github/solidjs/solid-start/tree/main/examples/bare
𖠰🌲 — Yesterday at 21:56
Perfect, TYSM!
Okay so it seems like solid doesn't freeze the whole app in a transition, just the returnvalues of createAsync, so I can use const isRouting = useIsRouting();to trigger my loading screen 🎉
Why is there no .loadingof the createAsyncthough that I can use to know specifically which one is loading? Because things are so slow that the user needs to be informed about the steps of progress
zulu — Yesterday at 22:05
it is odd, that createAsync does not have .loading
it is surprising that it has a limited capabilities of the createResource that it is based on
I think in the 2.0 the "capabilities" will be importable
import {isLoading, isPending, etc } from "solidjs
which you can use to test the state of the async signal

but no idea, why people use the createAsync if it is more limiting
in the current version
𖠰🌲 — Yesterday at 22:12
Ahh okay
𖠰🌲 — Yesterday at 22:12
Yeah I'm using it because it's recommended in the docs since it works better with query()
I switched back to createResource, however isLoading doesn't become trueeven when the resource is loading, presumably due to the transition being ongoing :/
Any idea how to get the actual value without patching solid router?
zulu — Yesterday at 22:15
I have seen that, I also think I saw that they might have patched it to expose the .latest I think

but you are not able to check for the state
𖠰🌲 — Yesterday at 22:15
Yeah .latestis exposed
zulu — Yesterday at 22:15
is isLoading exists in the 1.9?
𖠰🌲 — Yesterday at 22:16
Ah sorry I think it's just called resource.loading
But it exists in 1.9.5 when using createResource(not createAsync)
zulu — Yesterday at 22:20
yes, the createResource has few useful getters

.state
.loading
.latest
.error

but it looks like you are saying that when in transition it does not work as you expect
𖠰🌲 — Yesterday at 22:29
Exactly, here's a reproduction https://stackblitz.com/edit/github-3j8b6an5-tc2z1ort?file=src%2Froutes%2Findex.tsx

Initially when you enter the page it says "We are loading…".
When you enter something into the field and press submit

Expected behavior: It should say"We are loading…" again
Actual behavior: Nothing happens until 10 seconds later

I cannot use useIsRouting, as I need to know with granularity that exactly this createAsyncis loading.
StackBlitz
Solid-start Basic Example (forked) - StackBlitz
Run official live example code for Solid-start Basic, created by Solidjs on StackBlitz
Solid-start Basic Example (forked) - StackBlitz
mtt-4242 — Yesterday at 23:17
hi,
don't know how much you simplify the example, but i would use an action instead of a query

https://stackblitz.com/edit/github-3j8b6an5-xkvrnurl?file=src%2Froutes%2Findex.tsx

import {
action,
useAction,
useSubmission,
} from '@solidjs/router';
import { Show } from 'solid-js';

const doThingOnBackend = action(async (form: FormData) => {
'use server';
// Do heavy processing
await new Promise((r) => setTimeout(r, 10_000));
return We did a lot of thinking about ${form.get('field')} on the backend!;
});

export default function Home() {
const submit = useAction(doThingOnBackend);
const submition = useSubmission(doThingOnBackend);

return (
<>
<form
onSubmit={(e) => {
e.preventDefault();
submit(new FormData(e.currentTarget));
}}
>
Enter thing to think about

submit



Result of backend process is:{' '}
<Show
when={submition.pending}
fallback={<div style={{ color: 'red' }}>{submition.result}}
>
Loading...

</>
);
}
peerreynders — 00:41
You probably want a combination of pending() from useTransition() and useIsRouting()

⁠support⁠how to trigger on re…

isRouting() only catches navigations but revalidate-ions (which result from actions) of querys also cause transitions.
useTransition - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
useIsRouting - SolidDocs
Documentation for SolidJS, the signals-powered UI framework
ryansolid — 05:23
Yeah this a good argument for why having a singular global isPending for awkward. That's something that I did differently than React(mostly since we shipped first and this behavior was ambiguous in early React experiments).

In any case dealing with what we have now there are 2 approaches.

Use your own Transition and write to local state and write back to the search params. By awaiting the transition you can set your own isPending Signal on either side and show appropriate UI. This is how isRouting in the router works.

Wrap the Suspense with a keyed Show when={searchParams.userInput}. New Suspense boundaries don't participate in Transitions.

ryansolid — 05:27
I wanted to be careful here. Async isn't transitive in 1.0 and there are some awkward patterns having stuff like .loading and .error lead to. Things not triggering Suspense properly etc. I didn't want to introduce behavior in createAsync we wouldn't keep in 2.0. You can always use createResource.
zulu — 05:46
yeah, I understand that the API is not finalized in 2.0

I think the main problem stems from createAsync being preferable / recommended by the docs

I am not sure why the caching improvement was not possible
with the existing createResource

but the situation is the until the 2.0 API is settled the 1.0 createAsync might be held back
ryansolid — 05:51
I mean technically possible because I made createAsync on createResource, but awkward. Fetching side of createResource doesn't track so it wasn't composable. I wanted to trigger refetching based on keys with a simple wrapper. And createResource meant query woul need to interact with both sides, or be passed via a cache option or something.
In 1.0 model stuff like .loading and .error are overused. The only reason for .loading is to guard against transitions to allow tearing. Every other case should just use Suspense. .error was just a mistake.
zulu — 05:55
we also have .state I think, why is error a mistake?
ryansolid — 05:58
Yeah .state is probably the worst and I was convinced into it as a "why not?". But it leads to a ton of confusion. Because these reads basically guard against Suspense or ErrorBoundary but when something slips through the behavior makes no sense. It also messes with SSR a bit for that reason. Suspense let's us know when parts of the UI are ready.
zulu — 06:05
so you mean that the getter allow users to "take over", and by doing that the
is out of the loop for tracking when things are ready.

in a sense it allows users to create their own custom Suspense
but by doing that it will by pass, the built in functionality that Suspense carry internally

If I understand this correctly
ryansolid — 06:08
Yes. Like Streaming SSR, escaping Transitions, deferring mount effects. And its inconsistent especially in terms of error to give this ability just to createResource.
zulu — 07:16
@ryansolid does this make sense

how can something that happened in the future ( setTimeout,queueMicrotask) see the past value?
when the computed that ran before, already sees the new value.

https://stackblitz.com/edit/github-3j8b6an5-ss9vwqwg?file=src%2Froutes%2Findex.tsx

export default function Home() {
const [searchParams, setSearchParams] = useSearchParams();
const backendResult = createAsync(() => {
let r = doThingOnBackend(searchParams.userInput as string);

setTimeout(() => {
  console.log(performance.now());
  console.log('setTimeout', searchParams.userInput);
}, 0);
queueMicrotask(() => {
  console.log(performance.now());
  console.log('queueMicrotask', searchParams.userInput);
});
return r;

});

createComputed(() => {
console.log(performance.now());
console.log('createComputed', searchParams.userInput);
});

Image
zulu — 07:28
searchParams.userInput is set from 333 to 111
Image
zulu — 08:17
Ok, I guess it makes sense, it is just weird
the async code can not detect the transaction
so we get the non transaction value of the signal.
𖠰🌲 — 14:19
Hmm not sure if this lets me know specifically what createAsync is pending, but thanks
𖠰🌲 — 14:20
Ohh, this is a really nice solution, thank you! Might go with this, I do need it to do an initial submission on pageload though and am not sure if that will work correctly with SSR
𖠰🌲 — 14:22
Ahh makes sense. Yeah I think react router does a thing where it "transitions" (shows old state) for a hardcoded max of like one or two seconds and then still shows the loading UI if queries are pending, that would probably be an acceptable solution for my case if solid also implemented it.

I'll try to understand your solution 1, it might be the best one, since 2. would force everything to be re-created when userInput changes
Actually though I think 2 is a really nice an simple solution, since I suppose everything gets re-created anyways when entering the suspense fallback?
Going with the keyed show for now and accepting whatever cost it's to re-create my children, thank you all sooooo much for the help! ❤️ I didn't think I'd get away with a two line change for this and was very frustrated

@danieltroger
Copy link
Contributor Author

Don't know if you want this issue open to track finding a better solution? But I'm good with the workaround for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs triage
Projects
None yet
Development

No branches or pull requests

1 participant