solid icon indicating copy to clipboard operation
solid copied to clipboard

createMemo sometimes returns undefined when called during a transition

Open Brendonovich opened this issue 1 year ago • 5 comments

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

Brendonovich avatar Oct 31 '24 07:10 Brendonovich

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.

silen-z avatar Dec 30 '24 13:12 silen-z

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

Brendonovich avatar Dec 31 '24 07:12 Brendonovich

~~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

Brendonovich avatar Jan 12 '25 03:01 Brendonovich

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

silen-z avatar Jun 12 '25 22:06 silen-z

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:

  1. Try to remember to set a default memo value createMemo(/*your memo fn here*/, defaultValue). That eliminates the issue
  2. 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>

14gasher avatar Jul 11 '25 19:07 14gasher