Skip to content

Commit 92ce03a

Browse files
committed
chore(core): remove resource tasks 🎉
also deprecate `useResource$` and friends
1 parent 1d7ae37 commit 92ce03a

File tree

31 files changed

+343
-819
lines changed

31 files changed

+343
-819
lines changed

.changeset/slimy-swans-send.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+
DEPRECATION: `useResource$` and `<Resource />` are now deprecated. `useAsync$` is more efficient, more flexible, and easier to use. Use `concurrency: 0` to have the same behavior as `useResource$`.

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,13 +384,10 @@ import {
384384
export default component$(() => {
385385
const query = useSignal('busy');
386386
const jokes = useResource$<{ value: string }[]>(
387-
async ({ track, cleanup }) => {
387+
async ({ track, abortSignal }) => {
388388
track(() => query.value);
389-
// A good practice is to use `AbortController` to abort the fetching of data if
390-
// new request comes in. We create a new `AbortController` and register a `cleanup`
391-
// function which is called when this function re-runs.
392-
const controller = new AbortController();
393-
cleanup(() => controller.abort());
389+
// The abortSignal is automatically aborted when this function re-runs,
390+
// canceling any pending fetch requests.
394391

395392
if (query.value.length < 3) {
396393
return [];
@@ -399,7 +396,7 @@ export default component$(() => {
399396
const url = new URL('https://api.chucknorris.io/jokes/search');
400397
url.searchParams.set('query', query.value);
401398

402-
const resp = await fetch(url, { signal: controller.signal });
399+
const resp = await fetch(url, { signal: abortSignal });
403400
const json = (await resp.json()) as { result: { value: string }[] };
404401

405402
return json.result;

packages/docs/src/routes/tutorial/introduction/resource/index.mdx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,8 @@ Use [`useResource$()`](/docs/(qwik)/core/state/index.mdx) to set up how the data
3434
// Use `track` to trigger re-running of this data fetching function.
3535
track(() => github.org);
3636

37-
// A good practice is to use `AbortController` to abort the fetching of data if
38-
// new request comes in. We create a new `AbortController` and register a `cleanup`
39-
// function which is called when this function re-runs.
40-
const controller = new AbortController();
41-
cleanup(() => controller.abort());
37+
// The abortSignal is automatically provided and will be aborted when this
38+
// function re-runs, allowing you to cancel pending operations.
4239

4340
// Fetch the data and return the promises.
4441
return getRepositories(github.org, controller);

packages/docs/src/routes/tutorial/reactivity/resource/index.mdx

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,50 +16,39 @@ created_at: '2022-08-02T12:07:45Z'
1616

1717
For this tutorial, we would like to fetch the list of repositories for a given GitHub organization. To aid you, we have added the `getRepositories()` function to the bottom of the file. Your task is to use the `getRepositories()` function to fetch the list of repositories whenever the user updates the `org` input.
1818

19-
Qwik provides [`useResource$()`](/docs/(qwik)/core/state/index.mdx) and `<Resource>` to help you fetch and display data from the server. When fetching data the application can be in one of three states:
19+
Qwik provides [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) to help you fetch and display data from the server. When fetching data the application can be in one of three states:
2020

2121
- `pending`: the data is being fetched from the server => Render `loading...` indicator.
22-
- `resolved`: the data has successfully been fetched from the server => Render the data.
2322
- `rejected`: the data could not be fetched from the server due to an error => Render the error.
23+
- `resolved`: the data has successfully been fetched from the server => Render the data.
2424

25-
Use [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function to set up how the data is fetched from the server. Use `<Resource>` to display the data.
25+
Use [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) function to set up how the data is fetched from the server.
2626

2727
## Fetching data
2828

29-
Use [`useResource$()`](/docs/(qwik)/core/state/index.mdx) to set up how the data is fetched from the server.
30-
29+
Use [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) to set up how the data is fetched from the server.
3130
```jsx
32-
const reposResource = useResource$<string[]>(({ track, cleanup }) => {
31+
const reposResource = useAsync$<string[]>(({ track, abortSignal }) => {
3332
// We need a way to re-run fetching data whenever the `github.org` changes.
3433
// Use `track` to trigger re-running of this data fetching function.
35-
track(() => github.org);
36-
37-
// A good practice is to use `AbortController` to abort the fetching of data if
38-
// new request comes in. We create a new `AbortController` and register a `cleanup`
39-
// function which is called when this function re-runs.
40-
const controller = new AbortController();
41-
cleanup(() => controller.abort());
42-
43-
// Fetch the data and return the promises.
44-
return getRepositories(github.org, controller);
34+
const org = track(githubOrg);
35+
36+
// The abortSignal is automatically (lazily) provided and will be aborted when this
37+
// function re-runs, allowing you to cancel pending operations.
38+
return getRepositories(org, abortSignal);
4539
});
4640
```
4741

48-
The [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function returns a `ResourceReturn` object, which is a Promise-like object that can be serialized by Qwik. The [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function allows you to `track` store properties so that the [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function can be reactive on store changes. The `cleanup` function allows you to register a code that needs to be run to release resources from the previous run. Finally, the [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function returns a promise that will resolve to the value.
49-
42+
The [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) function returns an `AsyncSignal`, which is reactive state that can be serialized by Qwik. The [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) function allows you to `track` store properties so that the [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) function can be reactive on store changes. The `abortSignal` allows you to cancel pending operations when the function re-runs. Finally, the [`useAsync$()`](/docs/(qwik)/core/state/index.mdx) function returns a promise that will resolve to the value.
43+
5044
## Rendering data
5145

52-
Use `<Resource>` to display the data of the [`useResource$()`](/docs/(qwik)/core/state/index.mdx) function. The `<Resource>` allows you to render different content depending if the resource is `pending`, `resolved` or `rejected`.
46+
The `AsyncSignal` provides the `.loading` and `.error` reactive properties to allow you to render different content depending if the resource is `pending`, `resolved` or `rejected`.
5347

54-
On the server the `<Resource>` does not render `loading` state, instead, it pauses rendering until the resource is resolved and will always render as either `resolved` or `rejected`. (On the client, the `<Resource>` renders all states including `pending`.)
48+
During SSR, the rendering will pause until the `AsyncSignal` has loaded so it will always render as either `resolved` or `rejected`.
5549

5650
```jsx
57-
<Resource
58-
value={resourceToRender}
59-
onPending={() => <div>Loading...</div>}
60-
onRejected={(reason) => <div>Error: {reason}</div>}
61-
onResolved={(data) => <div>{data}</div>}
62-
/>
51+
{reposResource.loading && <div>Loading...</div> ? reposResource.error ? <div>Error: {reposResource.error}</div> : <div>{reposResource.value}</div>}
6352
```
6453

6554
## SSR vs Client

packages/docs/src/routes/tutorial/reactivity/resource/problem/app.tsx

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,19 @@
11
// @ts-ignore: Unused import
2-
import { component$, useStore, Resource, useResource$ } from '@qwik.dev/core';
2+
import { component$, useSignal, useAsync$ } from '@qwik.dev/core';
33

44
export default component$(() => {
5-
const github = useStore({
6-
org: 'QwikDev',
7-
});
5+
const githubOrg = useSignal('QwikDev');
86

9-
// Use useResource$() to set up how the data is fetched from the server.
7+
// Use useAsync$() to set up how the data is fetched from the server.
108
// See the example for Fetching Data in the text on the left.
119
// @ts-ignore: Unused declaration
12-
const reposResource = useResource$<string[]>(({ track, cleanup }) => {
10+
const reposResource = useAsync$<string[]>(({ track, abortSignal }) => {
1311
// We need a way to re-run fetching data whenever the `github.org` changes.
1412
// Use `track` to trigger re-running of the this data fetching function.
15-
track(() => github.org);
16-
17-
// A good practice is to use `AbortController` to abort the fetching of data if
18-
// new request comes in. We create a new `AbortController` and register a `cleanup`
19-
// function which is called when this function re-runs.
20-
const controller = new AbortController();
21-
cleanup(() => controller.abort());
13+
const org = track(githubOrg);
2214

2315
// Fetch the data and return the promises.
24-
return getRepositories(github.org, controller);
16+
return getRepositories(org, abortSignal);
2517
});
2618

2719
console.log('Render');
@@ -30,7 +22,7 @@ export default component$(() => {
3022
<p>
3123
<label>
3224
GitHub username:
33-
<input value={github.org} onInput$={(ev, el) => (github.org = el.value)} />
25+
<input bind:value={githubOrg} />
3426
</label>
3527
</p>
3628
<section>
@@ -40,7 +32,7 @@ export default component$(() => {
4032
<ul>
4133
{repos.map((repo) => (
4234
<li>
43-
<a href={`https://github.com/${github.org}/${repo}`}>{repo}</a>
35+
<a href={`https://github.com/${githubOrg.value}/${repo}`}>{repo}</a>
4436
</li>
4537
))}
4638
</ul>
@@ -52,11 +44,11 @@ export default component$(() => {
5244

5345
export async function getRepositories(
5446
username: string,
55-
controller?: AbortController
47+
abortSignal?: AbortSignal
5648
): Promise<string[]> {
5749
console.log('FETCH', `https://api.github.com/users/${username}/repos`);
5850
const resp = await fetch(`https://api.github.com/users/${username}/repos`, {
59-
signal: controller?.signal,
51+
signal: abortSignal,
6052
});
6153
console.log('FETCH resolved');
6254
const json = await resp.json();

packages/docs/src/routes/tutorial/reactivity/resource/solution/app.tsx

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,16 @@
11
/* eslint-disable no-console */
2-
import { component$, useStore, Resource, useResource$ } from '@qwik.dev/core';
2+
import { component$, useSignal, useAsync$ } from '@qwik.dev/core';
33

44
export default component$(() => {
5-
const github = useStore({
6-
org: 'QwikDev',
7-
});
5+
const githubOrg = useSignal('QwikDev');
86

9-
const reposResource = useResource$<string[]>(({ track, cleanup }) => {
7+
const reposResource = useAsync$<string[]>(({ track, abortSignal }) => {
108
// We need a way to re-run fetching data whenever the `github.org` changes.
119
// Use `track` to trigger re-running of this data fetching function.
12-
track(() => github.org);
13-
14-
// A good practice is to use `AbortController` to abort the fetching of data if
15-
// new request comes in. We create a new `AbortController` and register a `cleanup`
16-
// function which is called when this function re-runs.
17-
const controller = new AbortController();
18-
cleanup(() => controller.abort());
10+
const org = track(githubOrg);
1911

2012
// Fetch the data and return the promises.
21-
return getRepositories(github.org, controller);
13+
return getRepositories(org, abortSignal);
2214
});
2315

2416
console.log('Render');
@@ -27,36 +19,35 @@ export default component$(() => {
2719
<p>
2820
<label>
2921
GitHub username:
30-
<input value={github.org} onInput$={(ev, el) => (github.org = el.value)} />
22+
<input bind:value={githubOrg} />
3123
</label>
3224
</p>
3325
<section>
34-
<Resource
35-
value={reposResource}
36-
onPending={() => <>Loading...</>}
37-
onRejected={(error) => <>Error: {error.message}</>}
38-
onResolved={(repos) => (
39-
<ul>
40-
{repos.map((repo) => (
41-
<li>
42-
<a href={`https://github.com/${github.org}/${repo}`}>{repo}</a>
43-
</li>
44-
))}
45-
</ul>
46-
)}
47-
/>
26+
{reposResource.loading ? (
27+
<>Loading...</>
28+
) : reposResource.error ? (
29+
<>Error: {reposResource.error.message}</>
30+
) : (
31+
<ul>
32+
{reposResource.value?.map((repo) => (
33+
<li>
34+
<a href={`https://github.com/${githubOrg.value}/${repo}`}>{repo}</a>
35+
</li>
36+
))}
37+
</ul>
38+
)}
4839
</section>
4940
</main>
5041
);
5142
});
5243

5344
export async function getRepositories(
5445
username: string,
55-
controller?: AbortController
46+
abortSignal?: AbortSignal
5647
): Promise<string[]> {
5748
console.log('FETCH', `https://api.github.com/users/${username}/repos`);
5849
const resp = await fetch(`https://api.github.com/users/${username}/repos`, {
59-
signal: controller?.signal,
50+
signal: abortSignal,
6051
});
6152
console.log('FETCH resolved');
6253
const json = await resp.json();

packages/qwik/handlers.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
*
77
* Make sure that these handlers are listed in manifest.ts
88
*/
9-
export { _chk, _res, _run, _task, _val } from '@qwik.dev/core';
9+
export { _chk, _rsc, _res, _run, _task, _val } from '@qwik.dev/core';

packages/qwik/src/core/internal.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ export {
7171
export { useLexicalScope } from './use/use-lexical-scope.public';
7272
export { isTask as _isTask, scheduleTask as _task } from './use/use-task';
7373
export { _captures } from './shared/qrl/qrl-class';
74+
export { _rsc } from './use/use-resource';

packages/qwik/src/core/reactive-primitives/impl/async-signal-impl.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,39 @@ const log = (...args: any[]) =>
3232
console.log('ASYNC COMPUTED SIGNAL', ...args.map(qwikDebugToString));
3333

3434
/** Retains job metadata and also serves as the argument for the compute function */
35-
class AsyncJob<T> implements AsyncCtx {
35+
class AsyncJob<T> implements AsyncCtx<T> {
3636
/** First holds the compute promise and then the cleanup promise */
3737
$promise$: Promise<void> | null = null;
3838
$cleanupRequested$: boolean = false;
3939
$canWrite$: boolean = true;
40-
$track$: AsyncCtx['track'] | undefined;
41-
$cleanups$: Parameters<AsyncCtx['cleanup']>[0][] | undefined;
40+
$track$: AsyncCtx<T>['track'] | undefined;
41+
$cleanups$: Parameters<AsyncCtx<T>['cleanup']>[0][] | undefined;
4242
$abortController$: AbortController | undefined;
4343

4444
constructor(readonly $signal$: AsyncSignalImpl<T>) {}
4545

46-
get track(): AsyncCtx['track'] {
46+
get track(): AsyncCtx<T>['track'] {
4747
return (this.$track$ ||= trackFn(this.$signal$, this.$signal$.$container$));
4848
}
4949

5050
get abortSignal(): AbortSignal {
5151
return (this.$abortController$ ||= new AbortController()).signal;
5252
}
5353

54+
/** Backward compatible cache method for resource */
55+
cache(): void {
56+
console.error(
57+
'useResource cache() method does not do anything. Use `useAsync$` instead of `useResource$`, use the `pollMs` option for polling behavior.'
58+
);
59+
}
60+
61+
get previous(): T | undefined {
62+
const val = this.$signal$.$untrackedValue$;
63+
if (val !== NEEDS_COMPUTATION) {
64+
return val;
65+
}
66+
}
67+
5468
cleanup(callback: () => void) {
5569
if (typeof callback === 'function') {
5670
(this.$cleanups$ ||= []).push(callback);

packages/qwik/src/core/reactive-primitives/impl/signal-impl.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ export class SignalImpl<T = any> implements Signal<T> {
8282
(import.meta.env.TEST ? !isDomContainer(this.$container$) : isServer) &&
8383
addQrlToSerializationCtx(effectSubscriber, this.$container$);
8484
DEBUG && log('read->sub', pad('\n' + this.toString(), ' '));
85+
} else {
86+
DEBUG && log('read no sub', pad('\n' + this.toString(), ' '));
8587
}
86-
DEBUG && log('read no sub', pad('\n' + this.toString(), ' '));
8788
return val;
8889
}
8990

0 commit comments

Comments
 (0)