Skip to content

Commit f959dc8

Browse files
authored
Merge pull request #8107 from QwikDev/v2-blocking-task
feat: introduce deferUpdates option for useTask$
2 parents fc1dec4 + 0782caa commit f959dc8

File tree

11 files changed

+304
-23
lines changed

11 files changed

+304
-23
lines changed

.changeset/green-days-give.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik.dev/core': minor
3+
---
4+
5+
feat: introduce deferUpdates option for useTask$

packages/docs/src/routes/api/qwik/api.json

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,20 @@
22942294
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
22952295
"mdFile": "core.taskfn.md"
22962296
},
2297+
{
2298+
"name": "TaskOptions",
2299+
"id": "taskoptions",
2300+
"hierarchy": [
2301+
{
2302+
"name": "TaskOptions",
2303+
"id": "taskoptions"
2304+
}
2305+
],
2306+
"kind": "Interface",
2307+
"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>",
2308+
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts",
2309+
"mdFile": "core.taskoptions.md"
2310+
},
22972311
{
22982312
"name": "Tracker",
22992313
"id": "tracker",
@@ -2626,7 +2640,7 @@
26262640
}
26272641
],
26282642
"kind": "Function",
2629-
"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",
2643+
"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",
26302644
"editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task-dollar.ts",
26312645
"mdFile": "core.usetask_.md"
26322646
},

packages/docs/src/routes/api/qwik/index.mdx

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8825,6 +8825,48 @@ export type TaskFn = (ctx: TaskCtx) => ValueOrPromise<void | (() => void)>;
88258825
88268826
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts)
88278827
8828+
## TaskOptions
8829+
8830+
```typescript
8831+
export interface TaskOptions
8832+
```
8833+
8834+
<table><thead><tr><th>
8835+
8836+
Property
8837+
8838+
</th><th>
8839+
8840+
Modifiers
8841+
8842+
</th><th>
8843+
8844+
Type
8845+
8846+
</th><th>
8847+
8848+
Description
8849+
8850+
</th></tr></thead>
8851+
<tbody><tr><td>
8852+
8853+
[deferUpdates?](#)
8854+
8855+
</td><td>
8856+
8857+
</td><td>
8858+
8859+
boolean
8860+
8861+
</td><td>
8862+
8863+
_(Optional)_ Block the rendering of the component until the task completes. Default is `true`
8864+
8865+
</td></tr>
8866+
</tbody></table>
8867+
8868+
[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/use/use-task.ts)
8869+
88288870
## Tracker
88298871
88308872
Used to signal to Qwik which state should be watched for changes.
@@ -9955,7 +9997,7 @@ Use `useTask` to observe changes on a set of inputs, and then re-execute the `ta
99559997
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.
99569998
99579999
```typescript
9958-
useTask$: (fn: TaskFn) => void
10000+
useTask$: (fn: TaskFn, opts?: TaskOptions) => void
995910001
```
996010002
996110003
<table><thead><tr><th>
@@ -9981,6 +10023,19 @@ fn
998110023
998210024
</td><td>
998310025
10026+
</td></tr>
10027+
<tr><td>
10028+
10029+
opts
10030+
10031+
</td><td>
10032+
10033+
[TaskOptions](#taskoptions)
10034+
10035+
</td><td>
10036+
10037+
_(Optional)_
10038+
998410039
</td></tr>
998510040
</tbody></table>
998610041

packages/docs/src/routes/docs/(qwik)/core/tasks/index.mdx

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ contributors:
1919
- adamdbradley
2020
- aendel
2121
- jemsco
22-
updated_at: '2023-10-18T07:33:22Z'
22+
- varixo
23+
updated_at: '2025-11-08T03:33:22Z'
2324
created_at: '2023-03-31T02:40:50Z'
2425
---
2526

@@ -33,15 +34,17 @@ Tasks are meant for running asynchronous operations as part of component initial
3334
> **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:
3435
>
3536
> - Tasks are asynchronous.
36-
> - Task run on server and browser.
37+
> - Tasks run on server and browser.
3738
> - Tasks run before rendering and can block rendering.
39+
> - Subsequent task re-executions (when tracked state changes) block rendering by default, but can be configured to not block DOM updates with `deferUpdates: false`.
3840
3941
`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$()`.
4042

4143
The basic use case for `useTask$()` is to perform work on component initialization. `useTask$()` has these properties:
4244
- It can run on either the server or in the browser.
43-
- It runs before rendering and blocks rendering.
45+
- It runs before the initial rendering and blocks the initial render.
4446
- 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.
47+
- By default, subsequent re-executions (when tracked state changes) do block DOM updates unless `deferUpdates: false` is specified.
4548

4649
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).
4750

@@ -59,7 +62,7 @@ When the user interacts with the application, it resumes on the client-side, con
5962
6063
**In Qwik, there are only 3 lifecycle stages:**
6164

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

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

9497
`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.
9598

99+
### Task options
100+
101+
`useTask$()` accepts an optional second parameter of type `TaskOptions` to configure the task behavior.
102+
103+
```typescript
104+
interface TaskOptions {
105+
deferUpdates?: boolean;
106+
}
107+
```
108+
109+
#### `deferUpdates`
110+
111+
The `deferUpdates` option controls whether subsequent task re-executions (when tracked state changes) should block DOM updates.
112+
113+
**Default behavior (`deferUpdates: false` or not specified):**
114+
- **Initial render**: The task blocks rendering (same as default)
115+
- **Subsequent runs**: The task blocks DOM updates until it completes - the journal flush is deferred until the task finishes
116+
117+
**With `deferUpdates: true`:**
118+
- **Initial render**: The task blocks rendering (runs before the component renders for the first time)
119+
- **Subsequent runs**: The task runs asynchronously without blocking DOM updates
120+
96121
Additionally, this task can be reactive and will re-execute when **tracked** [state](/docs/(qwik)/core/state/index.mdx) changes.
97122

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

111136
> 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.
112137
113-
`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).
138+
`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.
114139

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

@@ -151,7 +176,7 @@ const delay = (time: number) => new Promise((res) => setTimeout(res, time));
151176
152177
Use `useTask$()` when you need to:
153178
- Run async tasks before rendering
154-
- Run code only once before the component is first rendered
179+
- Run code only once before the component is first rendered
155180
- Programmatically run side-effect code when state changes
156181

157182
> 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.

packages/docs/vite.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ export default defineConfig(() => {
144144
'qwik-image',
145145
// optimizing breaks the wasm import
146146
'@rolldown/browser',
147+
'@qwik.dev/devtools',
147148
],
148149
},
149150
preview: {

packages/qwik/src/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ export { useComputedQrl } from './use/use-computed';
143143
export { useSerializerQrl, useSerializer$ } from './use/use-serializer';
144144
export type { OnVisibleTaskOptions, VisibleTaskStrategy } from './use/use-visible-task';
145145
export { useVisibleTaskQrl } from './use/use-visible-task';
146-
export type { TaskCtx, TaskFn, Tracker } from './use/use-task';
146+
export type { TaskCtx, TaskFn, Tracker, TaskOptions } from './use/use-task';
147147
export type {
148148
ResourceProps,
149149
ResourceOptions,

packages/qwik/src/core/qwik.core.api.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,11 @@ export interface TaskCtx {
16621662
// @public (undocumented)
16631663
export type TaskFn = (ctx: TaskCtx) => ValueOrPromise<void | (() => void)>;
16641664

1665+
// @public (undocumented)
1666+
export interface TaskOptions {
1667+
deferUpdates?: boolean;
1668+
}
1669+
16651670
// @internal (undocumented)
16661671
export class _TextVNode extends _VNode {
16671672
constructor(flags: _VNodeFlags, parent: _ElementVNode | _VirtualVNode | null, previousSibling: _VNode | null | undefined, nextSibling: _VNode | null | undefined, textNode: Text | null, text: string | undefined);
@@ -1803,12 +1808,12 @@ export interface UseStylesScoped {
18031808
export const useStylesScopedQrl: (styles: QRL<string>) => UseStylesScoped;
18041809

18051810
// @public
1806-
export const useTask$: (fn: TaskFn) => void;
1811+
export const useTask$: (fn: TaskFn, opts?: TaskOptions) => void;
18071812

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

18131818
// @public
18141819
export const useVisibleTask$: (fn: TaskFn, opts?: OnVisibleTaskOptions) => void;

packages/qwik/src/core/shared/scheduler.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -737,11 +737,16 @@ This is often caused by modifying a signal in an already rendered component duri
737737
host
738738
) as ValueOrPromise<ChoreReturnValue<ChoreType.TASK>>;
739739
} else {
740-
returnValue = runTask(
741-
payload as Task<TaskFn, TaskFn>,
742-
container,
743-
host
744-
) as ValueOrPromise<ChoreReturnValue<ChoreType.TASK>>;
740+
const task = payload as Task<TaskFn, TaskFn>;
741+
returnValue = runTask(task, container, host) as ValueOrPromise<
742+
ChoreReturnValue<ChoreType.TASK>
743+
>;
744+
if (task.$flags$ & TaskFlags.RENDER_BLOCKING) {
745+
blockingChoresCount++;
746+
returnValue = maybeThen(returnValue, () => {
747+
blockingChoresCount--;
748+
});
749+
}
745750
}
746751
}
747752
break;

0 commit comments

Comments
 (0)