createMemo sometimes returns undefined when called during a transition
Describe the bug
In some cases, createMemo is returning undefined during navigation transitions. We've observed this mainly from using Kobalte components in our main route that gets navigated to after signing in, which happens in a transition. I don't have a small reproduction yet but will try put one together eventually.
Your Example Website or App
todo
Steps to Reproduce the Bug or Issue
todo
Expected behavior
The result of the memo should be calculated and returned instead of undefined
Screenshots or Videos
No response
Platform
- OS: macOS
- Browser: N/A
- Version: N/A
Additional context
No response
I've also encountered this. I'm using solid router and tanstack query in ssr: false app. Unfortunately I don't know how to create minimal reproduction.
In our case with tanstack router, it looks like the computation in createMemo is being executed while Transition.running === true, even though it's not part of the transition's sources, leading to neither .value nor .tValue being assigned to in writeSignal.
https://github.com/solidjs/solid/blob/4d824b08d8534d2a079f9ca4c1ea980684c05582/packages/solid/src/reactive/signal.ts#L1308-L1315
~~This is almost certainly related to https://github.com/solidjs/solid-router/issues/451~~
nvm looks like this is different, more likely a bug in solid router as if (Transition) is never true in the reproduction
I think I hit this again. So I made as minimal reproduction as I could. Check this CodeSandbox: https://codesandbox.io/p/devbox/lively-forest-5rsvd6
I consistently run into this issue. Stack is typically:
- solid-start w/ solid router
- data fetching with tanstack/solid-query
I typically do 2 things to help:
- Try to remember to set a default memo value
createMemo(/*your memo fn here*/, defaultValue). That eliminates the issue - Wrap the application in an ErrorBoundary that auto calls reset after 10ms in prod - that way if I forget any default vals, the reset typically will handle it. Example below using a leaky bucket to determine if need a full page reload (sometimes works better) or just a simple reset (could be improved by also leaky bucketing full page reloads and doing something else):
// outside the component
// Track error resets with a leaky bucket
const resetTimestamps: number[] = [];
function shouldReloadInsteadOfReset(): boolean {
const now = Date.now();
// Remove timestamps older than 500ms
while (resetTimestamps.length && now - resetTimestamps[0] > 500) {
resetTimestamps.shift();
}
// Add the current timestamp
resetTimestamps.push(now);
// If 5 or more resets happened in the last 500ms, trigger reload
return resetTimestamps.length >= 5;
}
// inside the component
<ErrorBoundary
fallback={(e, reset) => {
if (import.meta.env.DEV) {
return (
<div class="p-6 rounded-md bg-red-50 text-red-900 shadow-md border border-red-200 max-w-2xl mx-auto">
<h2 class="text-xl font-semibold mb-4 flex items-center gap-2">
🚨 Unexpected Error
</h2>
<div class="space-y-2 mb-6">
<p>
<strong>Name:</strong> {e.name || "UnknownError"}
</p>
<p>
<strong>Message:</strong>{" "}
{e.message || "No message provided."}
</p>
{e.cause && (
<p>
<strong>Cause:</strong>{" "}
{typeof e.cause === "string"
? e.cause
: JSON.stringify(e.cause)}
</p>
)}
</div>
<button class="btn btn-primary mb-6" onClick={() => reset()}>
🔄 Reset and try again
</button>
<div class="bg-white rounded p-4 shadow-inner overflow-x-auto max-h-96 border text-sm font-mono">
<p class="mb-2 text-gray-500 font-semibold">Stack trace:</p>
{e.stack
?.split("\n")
.map((line: string) => (
<div
class={`whitespace-pre-wrap ${
line.includes("/src")
? "font-bold text-blue-600"
: "text-gray-700"
}`}
>
{line}
</div>
)) || <div>No stack trace available.</div>}
</div>
</div>
);
} else {
createEffect(() => {
setTimeout(() => {
if (shouldReloadInsteadOfReset()) {
window.location.reload();
} else {
reset();
}
}, 10);
});
}
return null;
}}
>
{children}
</ErrorBoundary>