Skip to content

Commit

Permalink
Merge pull request #7 from cesarParra/fetch-when
Browse files Browse the repository at this point in the history
Fetch when resource option
  • Loading branch information
cesarParra committed Jun 6, 2024
2 parents 8e846f0 + 10c7f1f commit 47d047c
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 22 deletions.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,36 @@ const { data, refetch, mutate } = $resource(
);
```

#### Defining when to fetch and refetch data

There are situations when you don't want to fetch the data immediately when the component is created, or when you
don't want to refetch the data when the reactive values change.

For example, you might have an initial null value for a reactive value, and you only want to fetch the data when the
value is set to a non-null value. In this situation you don't want to waste a call to the server since you know the
data is not going to be used or will come back as null.

An additional option you can pass to the `$resource` function is `fetchWhen`. This is any function that returns
a boolean. If the function returns `true`, the data will be fetched. If it returns `false`, the data will not be
fetched.

```javascript
import { $signal, $resource, $effect } from "c/signals";
import getAccountDetails from "@salesforce/apex/ResourceController.getAccountDetails";

export const selectedAccountId = $signal(null);

export const { data: getAccount } = $resource(
getAccountDetails,
() => ({
accountId: selectedAccountId.value
}),
{
fetchWhen: () => selectedAccountId.value !== null
}
);
```

## Storage

By default, any created signal is stored in memory and will be lost when the component is destroyed. This behavior
Expand Down
14 changes: 10 additions & 4 deletions examples/demo-signals/lwc/demoSignals/apex-fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ export const { data: fetchContacts } = $resource(getContacts);

export const selectedAccountId = $signal(null);

$effect(() => console.log('selected Account Id', selectedAccountId.value));
$effect(() => console.log("selected Account Id", selectedAccountId.value));

export const { data: getAccount } = $resource(getAccountDetails, () => ({
accountId: selectedAccountId.value
}));
export const { data: getAccount } = $resource(
getAccountDetails,
() => ({
accountId: selectedAccountId.value
}),
{
fetchWhen: () => selectedAccountId.value
}
);

$effect(() =>
console.log("the account changed", JSON.stringify(getAccount.value, null, 2))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public without sharing class ResourceController {

@AuraEnabled(Cacheable=true)
public static List<Contact> getContacts() {
System.debug('getContacts() called');
return [SELECT Id, Name FROM Contact];
}
}
7 changes: 4 additions & 3 deletions force-app/lwc/signals/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,8 @@ function $resource(fn, source, options) {
let _previousParams;
const _signal = $signal(loadingState(_value));
// Optimistic updates are enabled by default
const optimisticMutate = options?.optimisticMutate ?? true;
const _optimisticMutate = options?.optimisticMutate ?? true;
const _fetchWhen = options?.fetchWhen ?? (() => true);
const execute = async () => {
_signal.value = loadingState(_value);
const derivedSource = source instanceof Function ? source() : source;
Expand All @@ -194,7 +195,7 @@ function $resource(fn, source, options) {
return;
}
try {
const data = await fn(derivedSource);
const data = _fetchWhen() ? await fn(derivedSource) : _value;
// Keep track of the previous value
_value = data;
_signal.value = {
Expand Down Expand Up @@ -231,7 +232,7 @@ function $resource(fn, source, options) {
data: _signal.readOnly,
mutate: (newValue) => {
const previousValue = _value;
if (optimisticMutate) {
if (_optimisticMutate) {
// If optimistic updates are enabled, update the value immediately
mutatorCallback(newValue);
}
Expand Down
116 changes: 110 additions & 6 deletions src/lwc/signals/__tests__/signals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,13 @@ describe("signals", () => {
});
});

test('the onMutate function can set an error', async () => {
test("the onMutate function can set an error", async () => {
const asyncFunction = async () => {
return 'done';
return "done";
};

const asyncReaction = async (newValue: string, _: string | null, mutate: (value: string | null, error?: unknown) => void) => {
mutate(null, 'An error occurred');
mutate(null, "An error occurred");
};

const { data: resource, mutate } = $resource(asyncFunction, undefined, {
Expand All @@ -305,17 +305,17 @@ describe("signals", () => {
await new Promise(process.nextTick);

expect(resource.value).toEqual({
data: 'done',
data: "done",
loading: false,
error: null
});

mutate('mutated');
mutate("mutated");

expect(resource.value).toEqual({
data: null,
loading: false,
error: 'An error occurred'
error: "An error occurred"
});
});
});
Expand Down Expand Up @@ -359,6 +359,110 @@ describe("signals", () => {
});
});

test("when the fetchWhen option is passed, it does not fetch when it evaluates to false", async () => {
const asyncFunction = async (params?: { [key: string]: unknown }) => {
return params?.["source"];
};

const source = $signal("changed");
const { data: resource } = $resource(asyncFunction, () => ({
source: source.value
}),
{
initialValue: "initial",
fetchWhen: () => false
});

expect(resource.value).toEqual({
data: "initial",
loading: false,
error: null
});

await new Promise(process.nextTick);

expect(resource.value).toEqual({
data: "initial",
loading: false,
error: null
});
});

test("when the fetchWhen option is passed, it fetches when it evaluates to true", async () => {
const asyncFunction = async (params?: { [key: string]: unknown }) => {
return params?.["source"];
};

const source = $signal("changed");
const { data: resource } = $resource(asyncFunction, () => ({
source: source.value
}),
{
initialValue: "initial",
fetchWhen: () => true
});

expect(resource.value).toEqual({
data: "initial",
loading: true,
error: null
});

await new Promise(process.nextTick);

expect(resource.value).toEqual({
data: "changed",
loading: false,
error: null
});
});

test("when the fetchWhen option is passed, it fetches when its value changes to true", async () => {
const asyncFunction = async (params?: { [key: string]: unknown }) => {
return params?.["source"];
};

const flagSignal = $signal(false);
const source = $signal("changed");
const { data: resource } = $resource(asyncFunction, () => ({
source: source.value
}),
{
initialValue: "initial",
fetchWhen: () => flagSignal.value
});

expect(resource.value).toEqual({
data: "initial",
loading: false,
error: null
});

await new Promise(process.nextTick);

expect(resource.value).toEqual({
data: "initial",
loading: false,
error: null
});

flagSignal.value = true;

expect(resource.value).toEqual({
data: "initial",
loading: true,
error: null
});

await new Promise(process.nextTick);

expect(resource.value).toEqual({
data: "changed",
loading: false,
error: null
});
});

test("can create custom storages", () => {
const useUndo = <T>(value: T) => {
const _valueStack: T[] = [];
Expand Down
20 changes: 12 additions & 8 deletions src/lwc/signals/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,14 @@ type MutatorCallback<T> = (value: T | null, error?: unknown) => void;

type OnMutate<T> = (newValue: T, oldValue: T | null, mutate: MutatorCallback<T>) => Promise<void> | void;

type FetchWhenPredicate = () => boolean;

type ResourceOptions<T> = {
initialValue?: T;
optimisticMutate?: boolean;
onMutate?: OnMutate<T>;
storage?: StorageFn<T>;
initialValue: T;
optimisticMutate: boolean;
onMutate: OnMutate<T>;
storage: StorageFn<T>;
fetchWhen: FetchWhenPredicate;
};

/**
Expand Down Expand Up @@ -231,7 +234,7 @@ type ResourceOptions<T> = {
function $resource<T>(
fn: (params?: { [key: string]: unknown }) => Promise<T>,
source?: UnknownArgsMap | (() => UnknownArgsMap),
options?: ResourceOptions<T>
options?: Partial<ResourceOptions<T>>
): ResourceResponse<T> {
function loadingState(data: T | null): AsyncData<T> {
return {
Expand All @@ -246,7 +249,8 @@ function $resource<T>(
let _previousParams: UnknownArgsMap | undefined;
const _signal = $signal<AsyncData<T>>(loadingState(_value));
// Optimistic updates are enabled by default
const optimisticMutate = options?.optimisticMutate ?? true;
const _optimisticMutate = options?.optimisticMutate ?? true;
const _fetchWhen = options?.fetchWhen ?? (() => true);

const execute = async () => {
_signal.value = loadingState(_value);
Expand All @@ -260,7 +264,7 @@ function $resource<T>(
}

try {
const data = await fn(derivedSource);
const data = _fetchWhen() ? await fn(derivedSource) : _value;
// Keep track of the previous value
_value = data;
_signal.value = {
Expand Down Expand Up @@ -300,7 +304,7 @@ function $resource<T>(
data: _signal.readOnly,
mutate: (newValue: T) => {
const previousValue = _value;
if (optimisticMutate) {
if (_optimisticMutate) {
// If optimistic updates are enabled, update the value immediately
mutatorCallback(newValue);
}
Expand Down

0 comments on commit 47d047c

Please sign in to comment.