diff --git a/e2e/qwik-e2e/tests/resource.e2e.ts b/e2e/qwik-e2e/tests/resource.e2e.ts index 2fca6b4c4c0..36ac34db22b 100644 --- a/e2e/qwik-e2e/tests/resource.e2e.ts +++ b/e2e/qwik-e2e/tests/resource.e2e.ts @@ -108,28 +108,28 @@ test.describe('resource serialization', () => { await expect(button1).toHaveText('PASS: Success 0'); await expect(button2).toHaveText('ERROR: Error: failed 0'); - await expect(button3).toHaveText('ERROR: Error: timeout 0'); + await expect(button3).toHaveText('ERROR: Error: timeout 100ms 0'); // Click button 1 await button1.click(); await expect(button1).toHaveText('PASS: Success 1'); await expect(button2).toHaveText('ERROR: Error: failed 0'); - await expect(button3).toHaveText('ERROR: Error: timeout 0'); + await expect(button3).toHaveText('ERROR: Error: timeout 100ms 0'); // Click button 2 await button2.click(); await expect(button1).toHaveText('PASS: Success 1'); await expect(button2).toHaveText('ERROR: Error: failed 1'); - await expect(button3).toHaveText('ERROR: Error: timeout 1'); + await expect(button3).toHaveText('ERROR: Error: timeout 100ms 1'); // Click button 2 await button2.click(); await expect(button1).toHaveText('PASS: Success 1'); await expect(button2).toHaveText('ERROR: Error: failed 2'); - await expect(button3).toHaveText('ERROR: Error: timeout 2'); + await expect(button3).toHaveText('ERROR: Error: timeout 100ms 2'); }); test('issue 2014 - resource signal resumability', async ({ page }) => { diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index 65a135480ef..034203d98e6 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -261,7 +261,7 @@ } ], "kind": "Interface", - "content": "An AsyncSignal holds the result of the given async function. If the function uses `track()` to track reactive state, and that state changes, the AsyncSignal is recalculated, and if the result changed, all tasks which are tracking the AsyncSignal will be re-run and all subscribers (components, tasks etc) that read the AsyncSignal will be updated.\n\nIf the async function throws an error, the AsyncSignal will capture the error and set the `error` property. The error can be cleared by re-running the async function successfully.\n\nWhile the async function is running, the `.loading` property will be set to `true`. Once the function completes, `loading` will be set to `false`.\n\nIf the value has not yet been resolved, reading the AsyncSignal will throw a Promise, which will retry the component or task once the value resolves.\n\nIf the value has been resolved, but the async function is re-running, reading the AsyncSignal will subscribe to it and return the last resolved value until the new value is ready. As soon as the new value is ready, the subscribers will be updated.\n\nIf the async function threw an error, reading the `.value` will throw that same error. Read from `.error` to check if there was an error.\n\n\n```typescript\nexport interface AsyncSignal extends ComputedSignal \n```\n**Extends:** [ComputedSignal](#computedsignal)<T>\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nerror\n\n\n\n\n\n\n\nError \\| undefined\n\n\n\n\nThe error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed.\n\n\n
\n\ninterval\n\n\n\n\n\n\n\nnumber\n\n\n\n\nStaleness/poll interval in ms. Writable and immediately effective.\n\n- \\*\\*Positive\\*\\*: Poll — re-compute after this many ms when subscribers exist. - \\*\\*Negative\\*\\*: Stale-only — mark stale after `|interval|` ms, no auto-recompute. - \\*\\*`0`\\*\\*: No staleness tracking or polling.\n\n\n
\n\nloading\n\n\n\n\n\n\n\nboolean\n\n\n\n\nWhether the signal is currently loading. This will trigger lazy loading of the signal, so you can use it like this:\n\n```tsx\nsignal.loading ? : signal.error ? : \n```\n\n\n
\n\n\n\n\n\n\n
\n\nMethod\n\n\n\n\nDescription\n\n\n
\n\n[abort(reason)](#asyncsignal-abort)\n\n\n\n\nAbort the current computation and run cleanups if needed.\n\n\n
\n\n[invalidate(info)](#asyncsignal-invalidate)\n\n\n\n\nUse this to force recalculation. If you pass `info`, it will be provided to the calculation function.\n\n\n
\n\n[promise()](#asyncsignal-promise)\n\n\n\n\nA promise that resolves when the value is computed or rejected.\n\n\n
", + "content": "An AsyncSignal holds the result of the given async function. If the function uses `track()` to track reactive state, and that state changes, the AsyncSignal is recalculated, and if the result changed, all tasks which are tracking the AsyncSignal will be re-run and all subscribers (components, tasks etc) that read the AsyncSignal will be updated.\n\nIf the async function throws an error, the AsyncSignal will capture the error and set the `error` property. The error can be cleared by re-running the async function successfully.\n\nWhile the async function is running, the `.loading` property will be set to `true`. Once the function completes, `loading` will be set to `false`.\n\nIf the value has not yet been resolved, reading the AsyncSignal will throw a Promise, which will retry the component or task once the value resolves.\n\nIf the value has been resolved, but the async function is re-running, reading the AsyncSignal will subscribe to it and return the last resolved value until the new value is ready. As soon as the new value is ready, the subscribers will be updated.\n\nIf the async function threw an error, reading the `.value` will throw that same error. Read from `.error` to check if there was an error.\n\n\n```typescript\nexport interface AsyncSignal extends ComputedSignal \n```\n**Extends:** [ComputedSignal](#computedsignal)<T>\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nerror\n\n\n\n\n\n\n\nError \\| undefined\n\n\n\n\nThe error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed. It does not trigger lazy loading of the signal.\n\n\n
\n\ninterval\n\n\n\n\n\n\n\nnumber\n\n\n\n\nStaleness/poll interval in ms. Writable and immediately effective.\n\n- \\*\\*Positive\\*\\*: Poll — re-compute after this many ms when subscribers exist. - \\*\\*Negative\\*\\*: Stale-only — mark stale after `|interval|` ms, no auto-recompute. - \\*\\*`0`\\*\\*: No staleness tracking or polling.\n\n\n
\n\nloading\n\n\n\n\n\n\n\nboolean\n\n\n\n\nWhether the signal is currently loading. This will trigger lazy loading of the signal, so you can use it like this:\n\n```tsx\nsignal.loading ? : signal.error ? : \n```\n\n\n
\n\nuntrackedError\n\n\n\n\n\n\n\nError \\| undefined\n\n\n\n\nLets you read the error state without subscribing to `.error` updates. It does not trigger lazy loading of the signal.\n\nSetting it will trigger listeners for `.error`.\n\n\n
\n\nuntrackedLoading\n\n\n\n\n\n\n\nboolean\n\n\n\n\nLets you read the loading state without subscribing to `.loading` updates. It also triggers lazy loading of the signal.\n\nSetting it will trigger listeners for `.loading`.\n\n\n
\n\n\n\n\n\n\n
\n\nMethod\n\n\n\n\nDescription\n\n\n
\n\n[abort(reason)](#asyncsignal-abort)\n\n\n\n\nAbort the current computation and run cleanups if needed.\n\n\n
\n\n[invalidate(info)](#asyncsignal-invalidate)\n\n\n\n\nUse this to force recalculation. If you pass `info`, it will be provided to the calculation function.\n\n\n
\n\n[promise()](#asyncsignal-promise)\n\n\n\n\nA promise that resolves when the value is computed or rejected.\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/signal.public.ts", "mdFile": "core.asyncsignal.md" }, @@ -275,7 +275,7 @@ } ], "kind": "Interface", - "content": "```typescript\nexport interface AsyncSignalOptions extends ComputedOptions \n```\n**Extends:** [ComputedOptions](#computedoptions)\n\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nallowStale?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise.\n\nWhen false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing.\n\nDefaults to `true`.\n\n\n
\n\nawaitPrevious?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ Wait for previous invocation to complete before running again.\n\nDefaults to `true`.\n\n\n
\n\nclientOnly?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When true, the async computation is postponed to the browser. On SSR, the signal remains INVALID and does not execute the function. On the client, it will compute on first read.\n\nDefaults to `false`.\n\n\n
\n\nconcurrency?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum number of concurrent computations. Use `0` for unlimited.\n\nDefaults to `1`.\n\n\n
\n\neagerCleanup?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When subscribers drop to 0, run cleanup in the next tick, instead of waiting for the function inputs to change.\n\nDefaults to `false`, meaning cleanup happens only when inputs change.\n\n\n
\n\ninitial?\n\n\n\n\n\n\n\nT \\| (() => T)\n\n\n\n\n_(Optional)_ Like useSignal's `initial`; prevents the throw on first read when uninitialized\n\n\n
\n\ninterval?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Controls staleness and polling behavior.\n\n- \\*\\*Positive\\*\\*: Re-run the function after `interval` ms if subscribers exist (polling). - \\*\\*Negative\\*\\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Consumers can check staleness and manually trigger recomputation. - \\*\\*`0`\\*\\* (default): No staleness tracking or polling.\n\n\n
\n\ntimeout?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum time in milliseconds to wait for the async computation to complete. If exceeded, the computation is aborted and an error is thrown.\n\nIf `0`, no timeout is applied.\n\nDefaults to `0`.\n\n\n
", + "content": "```typescript\nexport interface AsyncSignalOptions extends ComputedOptions \n```\n**Extends:** [ComputedOptions](#computedoptions)\n\n\n\n\n\n\n\n\n\n\n
\n\nProperty\n\n\n\n\nModifiers\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nallowStale?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise. Reactivity will then update the readers when the new value is ready.\n\nWhen false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing.\n\nNote that `interval` invalidations are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders during polling.\n\nThis option only affects manual invalidations via `invalidate()`, and `interval` invalidations where no new value will be loaded (`interval` is negative, or there are no subscribers).\n\nDefaults to `true`.\n\n\n
\n\nclientOnly?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When true, the async computation is postponed to the browser. On SSR, the signal remains INVALID and does not execute the function. On the client, it will compute on first read.\n\nDefaults to `false`.\n\n\n
\n\nconcurrency?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum number of concurrent computations. Use `0` for unlimited.\n\nDefaults to `1`.\n\n\n
\n\neagerCleanup?\n\n\n\n\n\n\n\nboolean\n\n\n\n\n_(Optional)_ When subscribers drop to 0, run cleanup in the next tick, instead of waiting for the function inputs to change.\n\nDefaults to `false`, meaning cleanup happens only when inputs change.\n\n\n
\n\ninitial?\n\n\n\n\n\n\n\nT \\| (() => T)\n\n\n\n\n_(Optional)_ Like useSignal's `initial`; prevents the throw on first read when uninitialized\n\n\n
\n\ninterval?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Controls staleness and polling behavior.\n\n- \\*\\*Positive\\*\\*: Re-run the function after `interval` ms if subscribers exist (polling). - \\*\\*Negative\\*\\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Recomputation happens when reading `.value` or `.loading`. - \\*\\*`0`\\*\\* (default): No staleness tracking or polling.\n\n\n
\n\ntimeout?\n\n\n\n\n\n\n\nnumber\n\n\n\n\n_(Optional)_ Maximum time in milliseconds to wait for the async computation to complete. If exceeded, the computation is aborted and an error is thrown.\n\nIf `0`, no timeout is applied.\n\nDefaults to `0`.\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/reactive-primitives/types.ts", "mdFile": "core.asyncsignaloptions.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx index 3c23ba78632..78d848c2a04 100644 --- a/packages/docs/src/routes/api/qwik/index.mdx +++ b/packages/docs/src/routes/api/qwik/index.mdx @@ -221,7 +221,7 @@ Error \| undefined -The error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed. +The error that occurred while computing the signal, if any. This will be cleared when the signal is successfully computed. It does not trigger lazy loading of the signal. @@ -265,6 +265,40 @@ signal.loading ? ( ); ``` + + + +untrackedError + + + + + +Error \| undefined + + + +Lets you read the error state without subscribing to `.error` updates. It does not trigger lazy loading of the signal. + +Setting it will trigger listeners for `.error`. + + + + +untrackedLoading + + + + + +boolean + + + +Lets you read the loading state without subscribing to `.loading` updates. It also triggers lazy loading of the signal. + +Setting it will trigger listeners for `.loading`. + @@ -345,26 +379,13 @@ boolean -_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise. +_(Optional)_ When true (default), the previous value is kept while the signal re-computes after invalidation, so reads return stale data instead of throwing a promise. Reactivity will then update the readers when the new value is ready. When false, invalidation clears the value so reads throw the computation promise (like the initial load), which is useful for navigations where showing old data would be confusing. -Defaults to `true`. - - - - -awaitPrevious? - - - - - -boolean - - +Note that `interval` invalidations are not affected by this option and will keep the old value while the new value is loading, to avoid flashing loaders during polling. -_(Optional)_ Wait for previous invocation to complete before running again. +This option only affects manual invalidations via `invalidate()`, and `interval` invalidations where no new value will be loaded (`interval` is negative, or there are no subscribers). Defaults to `true`. @@ -449,7 +470,7 @@ number _(Optional)_ Controls staleness and polling behavior. -- \*\*Positive\*\*: Re-run the function after `interval` ms if subscribers exist (polling). - \*\*Negative\*\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Consumers can check staleness and manually trigger recomputation. - \*\*`0`\*\* (default): No staleness tracking or polling. +- \*\*Positive\*\*: Re-run the function after `interval` ms if subscribers exist (polling). - \*\*Negative\*\*: Mark the value as stale after `|interval|` ms, but do NOT auto-recompute. Recomputation happens when reading `.value` or `.loading`. - \*\*`0`\*\* (default): No staleness tracking or polling. diff --git a/packages/docs/src/routes/docs/(qwik)/core/state/index.mdx b/packages/docs/src/routes/docs/(qwik)/core/state/index.mdx index 812c0d7d062..44452ee1b58 100644 --- a/packages/docs/src/routes/docs/(qwik)/core/state/index.mdx +++ b/packages/docs/src/routes/docs/(qwik)/core/state/index.mdx @@ -280,9 +280,14 @@ You can use this to instantiate a custom class. ### `useAsync$()` -`useAsync$()` is similar to `useComputed$()`, but it allows the compute function to be asynchronous. It returns an `AsyncSignal` that has the result of the async function. The common use case is to fetch data asynchronously, possibly based on other signals (you need to read those with `track()`). +`useAsync$()` is a very powerful primitive that allows you to create a computed value that is asynchronous. It is +similar to `useComputed$()`, but it allows the compute function to be asynchronous. -A common use case is to fetch data from an external API within the component, which can occur either on the server or the client. +It returns an `AsyncSignal` that has the result of the async function, which holds the future `.value` and also has reactive `.loading` and `.error` properties to track the state of the async function. + +The common use case is to fetch data asynchronously, possibly based on other signals (you need to read those with `track()`). This will happen during SSR and in the client, transparently. + +It also has built-in support for polling, which makes it trivial to show data that needs to be kept up to date. #### Example @@ -345,21 +350,12 @@ export default component$(() => { ``` -As we see in the example above, `useAsync$()` returns an `AsyncSignal` object that works like a reactive promise, containing the data and the async state. +As we see in the example above, `useAsync$()` returns an `AsyncSignal` object that works like a reactive promise, containing the data and the async state. Note that during SSR, the Promise will be awaited. The important thing to understand about `useAsync$` is that it executes on the initial component render (just like `useTask$`). Often times it is desirable to start fetching the data on the server as part of the initial HTTP request, even before the component is rendered. Fetching data based on the path as part of Server-Side Rendering (SSR) is a common and preferred method of data loading, typically handled by the [`routeLoader$`](/docs/(qwikrouter)/route-loader/index.mdx) API. `useAsync$` is more of a low-level API that is useful when you want to fetch data in the browser. -In many ways `useAsync$` is similar to `useTask$`. The big differences are: - -- `useAsync$` allows you to return a "value" and exposes it via the `AsyncSignal`. -- `useAsync$` does not block rendering while the value is being resolved. -- `useAsync$` provides reactive `.loading` and `.error` properties, which can be used to show loading spinners or error messages in the UI. -- `useAsync$` also provides an `abortSignal` that can be used to cancel the async operation when the component is destroyed or when the async function is re-run due to a change in tracked state. -- `useAsync$` has built-in support for polling with `interval`, which allows you to re-run the async function at a specified interval, as long as it is used somewhere. -- `useTask$` on the other hand is useful for running any code that doesn't need to return a value or directly affect the rendering of the component, implementing logic based on reactive state. - -See [`routeLoader$`](/docs/(qwikrouter)/route-loader/index.mdx) for fetching data early as part of initial HTTP request. +See [`routeLoader$`](/docs/(qwikrouter)/route-loader/index.mdx) for fetching data as part of initial HTTP request, before SSR starts. #### Channel example @@ -404,7 +400,15 @@ function useAsync$( abortSignal: AbortSignal; previous: T | undefined; }) => Promise, - options?: { initial?: T; interval?: number; concurrency?: number; timeout?: number; eagerCleanup?: boolean } + options?: { + initial?: T; // initial stale value + interval?: number; // polling interval in ms + concurrency?: number; // concurrency level, default is 1 + timeout?: number; // max time in ms to run the async function + clientOnly?: boolean; // do not run during SSR + eagerCleanup?: boolean; // run cleanup as soon as there are no subscribers + allowStale?: boolean; // allow stale values while the async function is re-running + } ): AsyncSignal; ``` @@ -414,22 +418,23 @@ Arguments: - The `abortSignal` is automatically aborted when the async function is re-run or the component is destroyed, which allows you to cancel any pending async operations. Best practice is to pass it to every API that supports it. Also, read from it after every await, and if it is aborted, stop the execution of the async function. (`if (abortSignal.aborted) return;`) -- `previous` is the previous resolved value of the async function, which can be useful to implement logic based on the previous value. +- `previous` is the previous resolved value of the async function, which can be useful to implement logic based on the previous value. If there is no previous value, it will be `undefined`. An error result will clear the value, as well as invalidating when `allowStale: false`. The callback passed to `useAsync$()` runs as soon as it is read. That means that if you use it in the JSX output, it will start during the initial render of that part of the JSX. So if you pass it to a component, it will start when that component is rendered, after tasks have run. If you want to start it immediately on initialization, you can call `asyncSignal.promise()` to start it immediately. This can also be used to await the completion, but you still have to read the value or error from the signal itself. - Options: -- `initial`: the initial value of the async signal before the async function resolves. This is useful to show some initial data while the async function is still loading. If not provided, reading the value before it's resolved will throw a `Promise`, which is Qwik's way of ensuring the current function will re-run when the value is available. -- `interval`: if provided, the async function will be re-run every `interval` milliseconds, as long as the async signal is used somewhere. This is useful to keep data fresh. -- `concurrency`: controls what happens when the async function is still running and it needs to be re-run (because of a change in tracked signals or because of polling). +- `initial`: the initial stale value of the async signal before the async function resolves. This is useful to show some initial data while the async function is still loading. If not provided, reading the value before it's resolved will throw a `Promise`, which is Qwik's way of ensuring the current function will re-run when the value is available. +- `interval`: if provided, the async function will be re-run every `interval` milliseconds, as long as the async signal is used somewhere. This is useful to keep data fresh. If the interval is negative, the value will be marked as stale after `|interval|` milliseconds, but the async function will not be automatically re-run. Instead, it will be re-run when `.value` or `.loading` is read. +- `concurrency`: controls what happens when the async function is still running and it needs to be re-run: - The default is `concurrency: 1`, which means that the currently running async function and its cleanups have to complete before a new one starts. - If set to a number, there can be be up to that number currently in flight before new calls need to wait for one of them to complete. `0` means no limit. - No matter the `concurrency` setting, whenever the signal is invalidated, the currently running async function will be aborted using the `abortSignal`. It's up to you to decide to use the signal or not. - - The cleanups are awaited, so if you do not want that, you should not return a Promise. -- `timeout`: if provided, the async function will be automatically aborted if it takes longer than the specified time to resolve. -- `eagerCleanup`: if set to `true`, the async function will be aborted as soon as there are no subscribers to the async signal, instead of waiting for the next time it needs to be re-run. This is useful to free up resources as soon as they are not needed anymore. + - Cleanups are awaited, so if you do not want that, you should not return a Promise from the cleanup function. +- `timeout`: if provided, the async function will be automatically aborted if it takes longer than the specified time in ms to resolve. +- `clientOnly`: if set to `true`, the async function will not run during SSR and reading from the value during SSR will throw an Error. +- `eagerCleanup`: if set to `true`, the cleanup will run as soon as there are no subscribers to the async signal, instead of waiting for the next time it needs to be re-run. This is useful to free up resources as soon as they are not needed anymore. +- `allowStale`: if set to `true`, reading `.value` when the signal is invalidated will return the previous value, even while the new value is loading. Thanks to reactivity, the reader will automatically get the new value once it's loaded. This is useful to avoid showing loading spinners. When set to `false`, invalidation will clear the value and make reads throw the computation promise until the new value is loaded; the exception is that during polling the stale value will be kept while the new value is loading. The returned `AsyncSignal` has the following (reactive) properties: @@ -437,7 +442,9 @@ The returned `AsyncSignal` has the following (reactive) properties: interface AsyncSignal { value: T; loading: boolean; + untrackedLoading: boolean; error: Error | undefined; + untrackedError: Error | undefined; interval: number; promise(): Promise; abort(reason?: any): void; @@ -446,7 +453,9 @@ interface AsyncSignal { - `value: T`: the resolved value (or initial value if not yet resolved). Note that reading this property before the async function has completed will throw a `Promise` for the result, which causes Qwik to wait until that resolves and then re-run the reading function. Writing a different value to this property will update the value and notify subscribers. - `loading: boolean`: whether the async function is currently running +- `untrackedLoading: boolean`: same as `loading`, but does not trigger reactivity when it changes - `error: Error | undefined`: the error if the async function failed +- `untrackedError: Error | undefined`: same as `error`, but does not trigger reactivity when it changes - `interval: number`: the current polling interval in milliseconds. This can be updated to change the polling interval or to stop polling by setting it to `0`. - `promise(): Promise`: a function that can be called to start the async function. It returns a promise that resolves when the async function completes, but not its result. That needs to be read from `value` or `error`. - `abort(reason?: any): void`: a function that can be called to abort the currently running async function and run cleanups if needed. The reason is passed to the abortSignal. diff --git a/packages/docs/src/routes/docs/errors/index.mdx b/packages/docs/src/routes/docs/errors/index.mdx new file mode 100644 index 00000000000..971581607be --- /dev/null +++ b/packages/docs/src/routes/docs/errors/index.mdx @@ -0,0 +1,224 @@ +--- +title: Error Codes | Reference +description: Reference for all Qwik internal error codes with descriptions and troubleshooting guidance. +--- + +# Qwik Error Codes + +In production, Qwik logs compact error codes like `Code(Q8)` with a link to this page. Below is a description of every code, what triggers it, and how to fix it. + +## Q0 + +**Error while serializing class or style attributes** + +Qwik could not convert a `class` or `style` value to a string. Ensure you pass a string, an object, or an array — not a function or symbol. + +## Q1 + +**Scheduler not found** + +Internal error: the Qwik scheduler was not available when an effect tried to run. This usually means the container was destroyed or never initialized. If you see this in application code, please file a bug. + +## Q2 + +**track() received object, without prop to track** + +You called `track(obj)` without specifying which property to track. Use `track(() => obj.prop)` or `track(signal)` instead. + +## Q3 + +**Only primitive and object literals can be serialized** + +You stored a value (class instance, function, DOM node, etc.) in state that Qwik needs to serialize. Move it behind a QRL (`$()`) or keep it in a non-serialized location. See [Serialization](). + +## Q4 + +**You can render over an existing q:container. Skipping render()** + +`render()` was called on an element that already hosts a Qwik container. Remove the existing container first, or render into a different element. + +## Q5 + +**QRL is not a function** + +A QRL resolved to something other than a function. Make sure the `$`-suffixed expression exports a function, and that the optimizer is running. + +## Q6 + +**Dynamic import not found** + +The chunk file referenced by a QRL could not be loaded. This usually means a build artifact is missing or the base path is misconfigured. Rebuild your project and verify that all chunks are deployed. + +## Q7 + +**Unknown type argument** + +Internal error: an unknown type was passed to a core utility. If you see this in application code, please file a bug. + +## Q8 + +**Actual value for useContext() can not be found** + +You called `useContext(id)` but no ancestor called `useContextProvider(id, value)`. Make sure the provider is above the consumer in the component tree, and that the context was used during SSR so its state was serialized. + +## Q9 + +**Invoking 'use\*()' method outside of invocation context** + +A `use*()` hook was called outside of a component or task scope. Hooks can only be called synchronously inside `component$`, `useTask$`, `useComputed$`, or similar Qwik entry points. + +## Q10 + +**Calling a 'use\*()' method outside 'component$(() => \{ HERE \})' is not allowed** + +Similar to Q9 but more specific: you called a hook in a place that is not inside a `component$` body or another `use*` hook. Move the call into the component function. See [Use Method Rules](/docs/core/tasks/#use-method-rules). + +## Q11 + +**The provided Context reference is not a valid context created by createContextId()** + +The value you passed to `useContext()` or `useContextProvider()` was not created with `createContextId()`. Make sure you are importing and passing the correct context object. + +## Q12 + +**SsrError(tag)** + +An error occurred during SSR while processing a specific JSX tag. Check the accompanying message for details about which element failed and why. + +## Q13 + +**QRLs can not be resolved because it does not have an attached container** + +A QRL tried to resolve but has no container context. This happens when a QRL is used outside of a rendered Qwik tree. Ensure QRLs are created and used inside a Qwik container. + +## Q14 + +**QRLs can not be dynamically resolved, because it does not have a chunk path** + +A QRL has no chunk information. This typically means the optimizer did not process the file. Ensure the Qwik Vite plugin is configured correctly. + +## Q15 + +**The JSX ref attribute must be a Signal** + +You passed a non-Signal value to the `ref` prop. Use `useSignal()` to create a ref: `const myRef = useSignal()`, then pass `ref={myRef}`. + +## Q16 + +**Deserialization of data type is not implemented** + +Internal serialization error: an unknown type ID was encountered during deserialization. This may indicate a version mismatch between SSR and client code. Rebuild and redeploy. + +## Q17 + +**Expected vnode for ref prop** + +Internal serialization error: a `ref` prop pointed to something other than a VNode during serialization. If you see this in application code, please file a bug. + +## Q18 + +**Cannot allocate data type** + +Internal serialization error: an unknown type ID was encountered during allocation. This may indicate corrupted serialized state or a version mismatch. Rebuild and redeploy. + +## Q19 + +**Missing root id** + +Internal serialization error: a root object has no assigned ID during serialization. If you see this in application code, please file a bug. + +## Q20 + +**Serialization of data type is not implemented** + +An object of an unsupported type was encountered during serialization. Only primitives, plain objects, arrays, Signals, Dates, URLs, RegExps, Maps, Sets, and explicitly serializable types can be serialized. See Q3 for guidance. + +## Q21 + +**Serialization Error: Unvisited** + +Internal error: an object that should have been visited during the serialization walk was missed. If you see this in application code, please file a bug. + +## Q22 + +**Missing QRL chunk** + +A QRL's chunk information was not available during serialization. This usually means the optimizer did not process the code correctly. Rebuild your project. + +## Q23 + +**The value of the textarea must be a string** + +You passed a non-string value to a `