Skip to content
Merged
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/green-days-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@qwik.dev/core': minor
---

feat: introduce deferUpdates option for useTask$
16 changes: 15 additions & 1 deletion packages/docs/src/routes/api/qwik/api.json
Original file line number Diff line number Diff line change
Expand Up @@ -2294,6 +2294,20 @@
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
"mdFile": "core.taskfn.md"
},
{
"name": "TaskOptions",
"id": "taskoptions",
"hierarchy": [
{
"name": "TaskOptions",
"id": "taskoptions"
}
],
"kind": "Interface",
"content": "```typescript\nexport interface TaskOptions \n```\n\n\n<table><thead><tr><th>\n\nProperty\n\n\n</th><th>\n\nModifiers\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\n[deferUpdates?](#)\n\n\n</td><td>\n\n\n</td><td>\n\nboolean\n\n\n</td><td>\n\n_(Optional)_ Block the rendering of the component until the task completes. Default is `true`\n\n\n</td></tr>\n</tbody></table>",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
"mdFile": "core.taskoptions.md"
},
{
"name": "Tracker",
"id": "tracker",
Expand Down Expand Up @@ -2626,7 +2640,7 @@
}
],
"kind": "Function",
"content": "Reruns the `taskFn` when the observed inputs change.\n\nUse `useTask` to observe changes on a set of inputs, and then re-execute the `taskFn` when those inputs change.\n\nThe `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun.\n\n\n```typescript\nuseTask$: (fn: TaskFn) => void\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\nfn\n\n\n</td><td>\n\n[TaskFn](#taskfn)\n\n\n</td><td>\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nvoid",
"content": "Reruns the `taskFn` when the observed inputs change.\n\nUse `useTask` to observe changes on a set of inputs, and then re-execute the `taskFn` when those inputs change.\n\nThe `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun.\n\n\n```typescript\nuseTask$: (fn: TaskFn, opts?: TaskOptions) => void\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\nfn\n\n\n</td><td>\n\n[TaskFn](#taskfn)\n\n\n</td><td>\n\n\n</td></tr>\n<tr><td>\n\nopts\n\n\n</td><td>\n\n[TaskOptions](#taskoptions)\n\n\n</td><td>\n\n_(Optional)_\n\n\n</td></tr>\n</tbody></table>\n\n**Returns:**\n\nvoid",
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task-dollar.ts",
"mdFile": "core.usetask_.md"
},
Expand Down
57 changes: 56 additions & 1 deletion packages/docs/src/routes/api/qwik/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8825,6 +8825,48 @@ export type TaskFn = (ctx: TaskCtx) => ValueOrPromise<void | (() => void)>;

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

## TaskOptions

```typescript
export interface TaskOptions
```

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

Property

</th><th>

Modifiers

</th><th>

Type

</th><th>

Description

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

[deferUpdates?](#)

</td><td>

</td><td>

boolean

</td><td>

_(Optional)_ Block the rendering of the component until the task completes. Default is `true`

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

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

## Tracker

Used to signal to Qwik which state should be watched for changes.
Expand Down Expand Up @@ -9955,7 +9997,7 @@ Use `useTask` to observe changes on a set of inputs, and then re-execute the `ta
The `taskFn` only executes if the observed inputs change. To observe the inputs, use the `obs` function to wrap property reads. This creates subscriptions that will trigger the `taskFn` to rerun.

```typescript
useTask$: (fn: TaskFn) => void
useTask$: (fn: TaskFn, opts?: TaskOptions) => void
```

<table><thead><tr><th>
Expand All @@ -9981,6 +10023,19 @@ fn

</td><td>

</td></tr>
<tr><td>

opts

</td><td>

[TaskOptions](#taskoptions)

</td><td>

_(Optional)_

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

Expand Down
37 changes: 31 additions & 6 deletions packages/docs/src/routes/docs/(qwik)/core/tasks/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ contributors:
- adamdbradley
- aendel
- jemsco
updated_at: '2023-10-18T07:33:22Z'
- varixo
updated_at: '2025-11-08T03:33:22Z'
created_at: '2023-03-31T02:40:50Z'
---

Expand All @@ -33,15 +34,17 @@ Tasks are meant for running asynchronous operations as part of component initial
> **Note**: Tasks are similar to `useEffect()` in React, but there are enough differences that we did not want to call them the same so as not to bring preexisting expectations about how they work. The main differences are:
>
> - Tasks are asynchronous.
> - Task run on server and browser.
> - Tasks run on server and browser.
> - Tasks run before rendering and can block rendering.
> - Subsequent task re-executions (when tracked state changes) block rendering by default, but can be configured to not block DOM updates with `deferUpdates: false`.

`useTask$()` should be your default go-to API for running either synchronous or asynchronous work as part of component initialization or state change. It is only when you can't achieve what you need with `useTask$()` that you should consider using `useVisibleTask$()` or `useResource$()`.

The basic use case for `useTask$()` is to perform work on component initialization. `useTask$()` has these properties:
- It can run on either the server or in the browser.
- It runs before rendering and blocks rendering.
- It runs before the initial rendering and blocks the initial render.
- If multiple tasks are running then they are run sequentially in the order they were registered. An asynchronous task will block the next task from running until it completes.
- By default, subsequent re-executions (when tracked state changes) do block DOM updates unless `deferUpdates: false` is specified.

Tasks can also be used to perform work when a component state changes. In this case, the task will rerun every time the tracked state changes. See: [`track()`](#track).

Expand All @@ -59,7 +62,7 @@ When the user interacts with the application, it resumes on the client-side, con

**In Qwik, there are only 3 lifecycle stages:**

- `Task` - run before rendering and when tracked state changes. `Tasks` run sequentially, and block rendering.
- `Task` - run before rendering and when tracked state changes. `Tasks` run sequentially, and block each other. They also can block rendering.
- `Render` - runs after `TASK` and before `VisibleTask`
- `VisibleTask` - runs after `Render` and when the component becomes visible

Expand Down Expand Up @@ -93,6 +96,28 @@ When the user interacts with the application, it resumes on the client-side, con

`useTask$()` registers a hook to be executed upon component creation, it will run at least once either in the server or in the browser, depending on where the component is initially rendered.

### Task options

`useTask$()` accepts an optional second parameter of type `TaskOptions` to configure the task behavior.

```typescript
interface TaskOptions {
deferUpdates?: boolean;
}
```

#### `deferUpdates`

The `deferUpdates` option controls whether subsequent task re-executions (when tracked state changes) should block DOM updates.

**Default behavior (`deferUpdates: false` or not specified):**
- **Initial render**: The task blocks rendering (same as default)
- **Subsequent runs**: The task blocks DOM updates until it completes - the journal flush is deferred until the task finishes

**With `deferUpdates: true`:**
- **Initial render**: The task blocks rendering (runs before the component renders for the first time)
- **Subsequent runs**: The task runs asynchronously without blocking DOM updates

Additionally, this task can be reactive and will re-execute when **tracked** [state](/docs/(qwik)/core/state/index.mdx) changes.

**Notice that any subsequent re-execution of the task will always happen in the browser**, because reactivity is a browser-only thing.
Expand All @@ -110,7 +135,7 @@ Additionally, this task can be reactive and will re-execute when **tracked** [st

> If `useTask$()` does not track any state, it will run **exactly once**, either in the server **or** in the browser (**not both**), depending where the component is initially rendered. Effectively behaving like an "on-mount" hook.

`useTask$()` will block the rendering of the component until after its async callback resolves, in other words, tasks execute sequentially even if they are asynchronous. (Only one task executes at a time).
`useTask$()` will block the rendering of the component until after its async callback resolves. Tasks execute sequentially even if they are asynchronous (only one task executes at a time within a component). Subsequent re-executions (when tracking state changes) run asynchronously by default and block rendering unless `deferUpdates: false` is set.

Take a look at the simplest use case of the task to run some asynchronous work on component initialization:

Expand Down Expand Up @@ -151,7 +176,7 @@ const delay = (time: number) => new Promise((res) => setTimeout(res, time));

Use `useTask$()` when you need to:
- Run async tasks before rendering
- Run code only once before the component is first rendered
- Run code only once before the component is first rendered
- Programmatically run side-effect code when state changes

> Note, if you're thinking about loading data using `fetch()` inside of `useTask$`, consider using [`useResource$()`](/docs/core/state/#useresource) instead. This API is more efficient in terms of leveraging SSR streaming and parallel data fetching.
Expand Down
1 change: 1 addition & 0 deletions packages/docs/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export default defineConfig(() => {
'qwik-image',
// optimizing breaks the wasm import
'@rolldown/browser',
'@qwik.dev/devtools',
],
},
preview: {
Expand Down
2 changes: 1 addition & 1 deletion packages/qwik/src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export { useComputedQrl } from './use/use-computed';
export { useSerializerQrl, useSerializer$ } from './use/use-serializer';
export type { OnVisibleTaskOptions, VisibleTaskStrategy } from './use/use-visible-task';
export { useVisibleTaskQrl } from './use/use-visible-task';
export type { TaskCtx, TaskFn, Tracker } from './use/use-task';
export type { TaskCtx, TaskFn, Tracker, TaskOptions } from './use/use-task';
export type {
ResourceProps,
ResourceOptions,
Expand Down
9 changes: 7 additions & 2 deletions packages/qwik/src/core/qwik.core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,11 @@ export interface TaskCtx {
// @public (undocumented)
export type TaskFn = (ctx: TaskCtx) => ValueOrPromise<void | (() => void)>;

// @public (undocumented)
export interface TaskOptions {
deferUpdates?: boolean;
}

// @internal (undocumented)
export class _TextVNode extends _VNode {
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, textNode: Text | null, text: string | undefined);
Expand Down Expand Up @@ -1803,12 +1808,12 @@ export interface UseStylesScoped {
export const useStylesScopedQrl: (styles: QRL<string>) => UseStylesScoped;

// @public
export const useTask$: (fn: TaskFn) => void;
export const useTask$: (fn: TaskFn, opts?: TaskOptions) => void;

// Warning: (ae-internal-missing-underscore) The name "useTaskQrl" should be prefixed with an underscore because the declaration is marked as @internal
//
// @internal (undocumented)
export const useTaskQrl: (qrl: QRL<TaskFn>) => void;
export const useTaskQrl: (qrl: QRL<TaskFn>, opts?: TaskOptions) => void;

// @public
export const useVisibleTask$: (fn: TaskFn, opts?: OnVisibleTaskOptions) => void;
Expand Down
15 changes: 10 additions & 5 deletions packages/qwik/src/core/shared/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -737,11 +737,16 @@ This is often caused by modifying a signal in an already rendered component duri
host
) as ValueOrPromise<ChoreReturnValue<ChoreType.TASK>>;
} else {
returnValue = runTask(
payload as Task<TaskFn, TaskFn>,
container,
host
) as ValueOrPromise<ChoreReturnValue<ChoreType.TASK>>;
const task = payload as Task<TaskFn, TaskFn>;
returnValue = runTask(task, container, host) as ValueOrPromise<
ChoreReturnValue<ChoreType.TASK>
>;
if (task.$flags$ & TaskFlags.RENDER_BLOCKING) {
blockingChoresCount++;
returnValue = maybeThen(returnValue, () => {
blockingChoresCount--;
});
}
}
}
break;
Expand Down
Loading