-
-
Notifications
You must be signed in to change notification settings - Fork 996
Description
Describe the bug
Under certain conditions, the fetcher promise
of 'createResource' in SSR can cause an initialization error when the execution time is short.
In 1.9.9, in syncblocks, and in async function blocks, if you have a promise with a short execution time as a fetcher, hydration fails with a key mismatch.
I borrowed the example from another issue, which is somewhat related.
You must access data.loading
before data()
.
For example:
async :
const sleep = (ms: number) => new Promise(res => setTimeout(res, ms));
const [data] = createResource(refetchTrigger, async () => {
await sleep(1);
return { foo: 421 };
});
return (
<Suspense>
<Show when={!data.loading && data()}>
<context.Provider value={createStore(data())}>
<Main />
</context.Provider>
</Show>
</Suspense>
);
sync :
const [data] = createResource(refetchTrigger, () => {
return Promise.resolve({ foo: 499 });
});
// ...same here
I guess that the problem is that the promise resolves before it reaches some stable stage of hydration.
I've documented the problematic situations below.
Your Example Website or App
https://codeberg.org/iacore/kanban/src/branch/solidstart-debug/src/app.tsx
Steps to Reproduce the Bug or Issue
- Specify a log point.
// packages/solid/src/server/rendering.ts
// At the end of createResource
// ...
p = p
.then(res => {
console.log(`res`,res) // logger
read.loading = false;
read.state = "ready";
ctx.resources[id].data = res;
p = null;
notifySuspense(contexts);
return res;
})
.catch(err => {
read.loading = false;
read.state = "errored";
read.error = error = castError(err);
p = null;
notifySuspense(contexts);
throw error;
});
if (ctx.serialize) ctx.serialize(id, p, options.deferStream);
return p;
}
return handleResolvedValue(p, ctx, id);
}
if (options.ssrLoadFrom !== "initial") load();
const ref = [read, { refetch: load, mutate: (v: T) => (value = v) }] as ResourceReturn<T>;
if (p) resource.ref = ref;
console.log('returning ref', id) // logger
return ref;
}
- Reduce execution time.
const [data] = createResource(refetchTrigger, async () => { // or resolved Promise in sync block
await sleep(1); // short time
return { foo: 421 };
});
return (
<Suspense>
<Show when={!data.loading && data()}>
<context.Provider value={createStore(data())}>
<Main />
</context.Provider>
</Show>
</Suspense>
);
- Run
// console.log
returning ref 00000000100000010
returning ref 0000000010000020000000
res { foo: 421 }
returning ref 1
res Hello title!
res Hello title!
returning ref 00000000100000010
No route matched for preloading js assets
returning ref 1
res Hello title!
res Hello title!
returning ref 00000000100000010
No route matched for preloading js assets
returning ref 1
res Hello title!
res Hello title!
When latency is greater than 300 ms:
- Increase the execution time
const [data] = createResource(refetchTrigger, async () => {
await sleep(300); // Change Here
return { foo: 421 };
});
- Run
// console.log
returning ref 00000000100000010
res Hello title!
returning ref 0000000010000020000000
returning ref 1
res Hello title!
res { foo: 421 }
This is thought to be a problem when the function execution time, whether synchronous or asynchronous, is shorter than the completion time of any process in the hydration.
Expected behavior
It should run normally without any errors.
Platform
- OS: [Windows]
- Browser: [Chrome]
- Version: [139.0.7258.68 (Official Build) (64-bit)]
Additional context
Shouldn't we call data() first? You can.
For example, let's reverse the order in which the values are called in the Show component in our example.
// ...
<Show when={data() && !data.loading}> // let's reverse the order!
// ...
And the log (keeping the logging points from above) would look like this
returning ref 00000000100000010
returning ref 0000000010000020000000
res { foo: 421 }
returning ref 1
res Hello title!
res Hello title!
But there's a potential problem here. The problem is that when the fetcher
of createResource
is executed, it is before any other component
that interacts with the client or interacts between the client and the server has completed its initialization.
If there is some latency, the (probably ideal) log would be:
returning ref 00000000100000010
returning ref 0000000010000020000000
returning ref 1
res Hello title!
res Hello title!
res { foo: 421 }
I've only been looking at this for a short time, so maybe this was already taken into account at design time, but in my experience this is an unexpected and unwanted situation. This is because it has the potential to lead to unexpected results, as in this case of approaching loading
first. So I'm raising it as an issue.
Alternatively, if we were to adhere to this usage, we would need to document that the order of calling these functions should be respected instead.
However, this won't solve:
- Calling the entire
data()
, which is unknown how large and massive it might be, is a concern. - People expect to be able to call
isLoading
,loading
, etc, to determine availability by accessing them first. - Since
loading
is only available after initialization is complete, the logic of loading at initialization and loading on refresh is more separated, which can increase complexity.
Thanks!
Related Issues: #2132, solidjs/solid-start#1941