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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,11 +495,19 @@ This is light wrapper over `createResource` that aims to serve as stand-in for a
const user = createAsync((currentValue) => getUser(params.id))
```

It also preserves `latest` field from `createResource`. Note that it will be removed in the future.

```jsx
const user = createAsync((currentValue) => getUser(params.id))
return <h1>{user.latest.name}</h1>;
```

Using `cache` in `createResource` directly won't work properly as the fetcher is not reactive and it won't invalidate properly.

### `createAsyncStore`

Similar to `createAsync` except it uses a deeply reactive store. Perfect for applying fine-grained changes to large model data that updates.
It also supports `latest` field which will be removed in the future.

```jsx
const todos = createAsyncStore(() => getTodos());
Expand Down
42 changes: 34 additions & 8 deletions src/data/createAsync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,56 @@ import { type Accessor, createResource, sharedConfig, type Setter, untrack } fro
import { createStore, reconcile, type ReconcileOptions, unwrap } from "solid-js/store";
import { isServer } from "solid-js/web";

/**
* As `createAsync` and `createAsyncStore` are wrappers for `createResource`,
* this type allows to support `latest` field for these primitives.
* It will be removed in the future.
*/
export type AccessorWithLatest<T> = {
(): T;
latest: T;
}

export function createAsync<T>(
fn: (prev: T) => Promise<T>,
options: {
name?: string;
initialValue: T;
deferStream?: boolean;
}
): Accessor<T>;
): AccessorWithLatest<T>;
export function createAsync<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
}
): Accessor<T | undefined>;
): AccessorWithLatest<T | undefined>;
export function createAsync<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
name?: string;
initialValue?: T;
deferStream?: boolean;
}
): Accessor<T | undefined> {
): AccessorWithLatest<T | undefined> {
let resource: () => T;
let prev = () => !resource || (resource as any).state === "unresolved" ? undefined : (resource as any).latest;
[resource] = createResource(
() => subFetch(fn, untrack(prev)),
v => v,
options as any
);
return () => resource();

const resultAccessor: AccessorWithLatest<T> = (() => resource()) as any;
Object.defineProperty(resultAccessor, 'latest', {
get() {
return (resource as any).latest;
}
})

return resultAccessor;
}

export function createAsyncStore<T>(
Expand All @@ -47,7 +65,7 @@ export function createAsyncStore<T>(
deferStream?: boolean;
reconcile?: ReconcileOptions;
}
): Accessor<T>;
): AccessorWithLatest<T>;
export function createAsyncStore<T>(
fn: (prev: T | undefined) => Promise<T>,
options?: {
Expand All @@ -56,7 +74,7 @@ export function createAsyncStore<T>(
deferStream?: boolean;
reconcile?: ReconcileOptions;
}
): Accessor<T | undefined>;
): AccessorWithLatest<T | undefined>;
export function createAsyncStore<T>(
fn: (prev: T | undefined) => Promise<T>,
options: {
Expand All @@ -65,7 +83,7 @@ export function createAsyncStore<T>(
deferStream?: boolean;
reconcile?: ReconcileOptions;
} = {}
): Accessor<T | undefined> {
): AccessorWithLatest<T | undefined> {
let resource: () => T;
let prev = () => !resource || (resource as any).state === "unresolved" ? undefined : unwrap((resource as any).latest);
[resource] = createResource(
Expand All @@ -76,7 +94,15 @@ export function createAsyncStore<T>(
storage: (init: T | undefined) => createDeepSignal(init, options.reconcile)
} as any
);
return () => resource();

const resultAccessor: AccessorWithLatest<T> = (() => resource()) as any;
Object.defineProperty(resultAccessor, 'latest', {
get() {
return (resource as any).latest;
}
})

return resultAccessor;
}

function createDeepSignal<T>(value: T | undefined, options?: ReconcileOptions) {
Expand Down
2 changes: 1 addition & 1 deletion src/data/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { createAsync, createAsyncStore } from "./createAsync.js";
export { createAsync, createAsyncStore, type AccessorWithLatest } from "./createAsync.js";
export { action, useSubmission, useSubmissions, useAction, type Action } from "./action.js";
export { cache, revalidate, type CachedFunction } from "./cache.js";
export { redirect, reload, json } from "./response.js";
Expand Down