Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silly-jobs-hide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@builder.io/qwik': minor
---

feat: add `useAsyncComputed$`. Use it instead of `useComputed$` when the computation is async. There is a `track()` function to ensure tracking of signals.
32 changes: 30 additions & 2 deletions packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts",
"mdFile": "qwik.correctedtoggleevent.md"
},
{
"name": "createAsyncComputed$",
"id": "createasynccomputed_",
"hierarchy": [
{
"name": "createAsyncComputed$",
"id": "createasynccomputed_"
}
],
"kind": "Function",
"content": "Returns read-only signal that updates when signals used in the `AsyncComputedFn` change. Unlike useAsyncComputed$, this is not a hook and it always creates a new signal.\n\n\n```typescript\ncreateAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\nAsyncComputedFn&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[Signal](#signal)<!-- -->&lt;Awaited&lt;T&gt;&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
"mdFile": "qwik.createasynccomputed_.md"
},
{
"name": "createComputed$",
"id": "createcomputed_",
Expand Down Expand Up @@ -1774,7 +1788,7 @@
}
],
"kind": "Function",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[JSXNode](#jsxnode)<!-- -->&lt;'script'&gt;",
"content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nopts\n\n\n</td><td>\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nJSXNode&lt;'script'&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts",
"mdFile": "qwik.prefetchserviceworker.md"
},
Expand Down Expand Up @@ -3024,6 +3038,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/state/common.ts",
"mdFile": "qwik.unwrapstore.md"
},
{
"name": "useAsyncComputed$",
"id": "useasynccomputed_",
"hierarchy": [
{
"name": "useAsyncComputed$",
"id": "useasynccomputed_"
}
],
"kind": "Function",
"content": "Returns a computed signal which is calculated from the given async function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.\n\nThe function can be asynchronous and receives a `track` function to observe changes.\n\n\n```typescript\nuseAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\nAsyncComputedFn&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[Signal](#signal)<!-- -->&lt;Awaited&lt;T&gt;&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
"mdFile": "qwik.useasynccomputed_.md"
},
{
"name": "useComputed$",
"id": "usecomputed_",
Expand All @@ -3034,7 +3062,7 @@
}
],
"kind": "Function",
"content": "Returns a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.\n\nThe function must be synchronous and must not have any side effects.\n\nAsync functions are deprecated because:\n\n- When calculating the first time, it will see it's a promise and it will restart the render function. - Qwik can't track used signals after the first await, which leads to subtle bugs. - Both `useTask$` and `useResource$` are available, without these problems.\n\nIn v2, async functions won't work.\n\n\n```typescript\nuseComputed$: <T>(qrl: ComputedFn<T>) => Signal<Awaited<T>>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n[ComputedFn](#computedfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[Signal](#signal)<!-- -->&lt;Awaited&lt;T&gt;&gt;",
"content": "Returns a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.\n\nThe function must be synchronous and must not have any side effects. If you need a version that accepts async functions, use `useAsyncComputed$`<!-- -->.\n\n\n```typescript\nuseComputed$: <T>(qrl: ComputedFn<T>) => Signal<Awaited<T>>\n```\n\n\n<table><thead><tr><th>\n\nParameter\n\n\n</th><th>\n\nType\n\n\n</th><th>\n\nDescription\n\n\n</th></tr></thead>\n<tbody><tr><td>\n\nqrl\n\n\n</td><td>\n\n[ComputedFn](#computedfn)<!-- -->&lt;T&gt;\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\n[Signal](#signal)<!-- -->&lt;Awaited&lt;T&gt;&gt;",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
"mdFile": "qwik.usecomputed_.md"
},
Expand Down
90 changes: 83 additions & 7 deletions packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,46 @@ Description

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/render/jsx/types/jsx-qwik-attributes.ts)

## createAsyncComputed$

Returns read-only signal that updates when signals used in the `AsyncComputedFn` change. Unlike useAsyncComputed$, this is not a hook and it always creates a new signal.

```typescript
createAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>;
```

<table><thead><tr><th>

Parameter

</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

qrl

</td><td>

AsyncComputedFn&lt;T&gt;

</td><td>

</td></tr>
</tbody></table>

**Returns:**

[Signal](#signal)&lt;Awaited&lt;T&gt;&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts)

## createComputed$

> Warning: This API is now obsolete.
Expand Down Expand Up @@ -3667,7 +3707,7 @@ opts

**Returns:**

[JSXNode](#jsxnode)&lt;'script'&gt;
JSXNode&lt;'script'&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts)

Expand Down Expand Up @@ -10156,17 +10196,53 @@ T

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/state/common.ts)

## useComputed$
## useAsyncComputed$

Returns a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.
Returns a computed signal which is calculated from the given async function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.

The function can be asynchronous and receives a `track` function to observe changes.

```typescript
useAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>;
```

The function must be synchronous and must not have any side effects.
<table><thead><tr><th>

Async functions are deprecated because:
Parameter

- When calculating the first time, it will see it's a promise and it will restart the render function. - Qwik can't track used signals after the first await, which leads to subtle bugs. - Both `useTask$` and `useResource$` are available, without these problems.
</th><th>

Type

</th><th>

Description

</th></tr></thead>
<tbody><tr><td>

qrl

</td><td>

AsyncComputedFn&lt;T&gt;

</td><td>

</td></tr>
</tbody></table>

**Returns:**

[Signal](#signal)&lt;Awaited&lt;T&gt;&gt;

[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts)

## useComputed$

Returns a computed signal which is calculated from the given function. A computed signal is a signal which is calculated from other signals. When the signals change, the computed signal is recalculated, and if the result changed, all tasks which are tracking the signal will be re-run and all components that read the signal will be re-rendered.

In v2, async functions won't work.
The function must be synchronous and must not have any side effects. If you need a version that accepts async functions, use `useAsyncComputed$`.

```typescript
useComputed$: <T>(qrl: ComputedFn<T>) => Signal<Awaited<T>>;
Expand Down
11 changes: 11 additions & 0 deletions packages/docs/src/routes/docs/(qwik)/components/state/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ export default component$(() => {
In Qwik, there are two ways to create computed values, each with a different use case (in order of preference):

1. `useComputed$()`: `useComputed$()` is the preferred way of creating computed values. Use it when the computed value can be derived synchronously purely from the source state (current application state). For example, creating a lowercase version of a string or combining first and last names into a full name.
There is also an async version of `useComputed$()`, called `useAsyncComputed$()`. It allows passing async functions and the signal value is the resolved result.

2. [`useResource$()`](/docs/(qwik)/components/state/index.mdx#useresource): `useResource$()` is used when the computed value is asynchronous or the state comes from outside of the application. For example, fetching the current weather (external state) based on a current location (application internal state).

Expand Down Expand Up @@ -260,6 +261,16 @@ export default component$(() => {

> **NOTE** Because `useComputed$()` is synchronous it is not necessary to explicitly track the input signals.

### `useAsyncComputed$()`

Use `useAsyncComputed$()` to create a computed value that is derived asynchronously. The signal value is the resolved result, not the Promise.

It behaves more like `useTask$` in that it does not track any signals automatically. You must explicitly track the signals that should cause the async function to re-run, using the `track` function that is passed in the argument.

If you read the signal value before the async function resolves, it will stop execution of the current function and re-run that function when the computed result is known.

Therefore you must take care not to read the signal value in the same function that sets it, to avoid re-running the function. Passing the signal value into JSX is safe, because Qwik will know to pause the JSX rendering until the value is known.

### `useResource$()`

Use `useResource$()` to create a computed value that is derived asynchronously. It's the asynchronous version of `useComputed$()`, which includes the `state` of the resource (loading, resolved, rejected) on top of the value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Below, only the `useComputed$` function will re-run on any `count.value` change:
```tsx title="Optimal Implementation"
export default component$(() => {
const count = useSignal(1);
const dobuleCount = useComputed$(() => count.value*2);
const doubleCount = useComputed$(() => count.value*2);
return (
<div>{doubleCount.value}</div>
);
Expand Down
11 changes: 10 additions & 1 deletion packages/qwik/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,16 @@ export type { ResourceProps, ResourceOptions } from './use/use-resource';
export { useResource$, useResourceQrl, Resource } from './use/use-resource';
export { useTask$, useTaskQrl } from './use/use-task';
export { useVisibleTask$, useVisibleTaskQrl } from './use/use-task';
export { useComputed$, useComputedQrl, createComputed$, createComputedQrl } from './use/use-task';
export {
useComputed$,
useComputedQrl,
createComputed$,
createComputedQrl,
useAsyncComputed$,
useAsyncComputedQrl,
createAsyncComputed$,
createAsyncComputedQrl,
} from './use/use-task';
export { useErrorBoundary } from './use/use-error-boundary';
export type { ErrorBoundaryStore } from './render/error-handling';

Expand Down
18 changes: 18 additions & 0 deletions packages/qwik/src/core/qwik.core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,16 @@ export interface CorrectedToggleEvent extends Event {
readonly prevState: 'open' | 'closed';
}

// Warning: (ae-forgotten-export) The symbol "AsyncComputedFn" needs to be exported by the entry point index.d.ts
//
// @public
export const createAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>;

// Warning: (ae-internal-missing-underscore) The name "createAsyncComputedQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export const createAsyncComputedQrl: <T>(qrl: QRL<AsyncComputedFn<T>>) => Signal<Awaited<T>>;

// @public @deprecated
export const createComputed$: <T>(qrl: ComputedFn<T>) => Signal<Awaited<T>>;

Expand Down Expand Up @@ -1640,6 +1650,14 @@ export const untrack: <T>(fn: () => T) => T;
// @public
export const unwrapStore: <T>(proxy: T) => T;

// @public
export const useAsyncComputed$: <T>(qrl: AsyncComputedFn<T>) => Signal<Awaited<T>>;

// Warning: (ae-internal-missing-underscore) The name "useAsyncComputedQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export const useAsyncComputedQrl: <T>(qrl: QRL<AsyncComputedFn<T>>) => Signal<Awaited<T>>;

// @public
export const useComputed$: <T>(qrl: ComputedFn<T>) => Signal<Awaited<T>>;

Expand Down
Loading
Loading