Skip to content

Commit

Permalink
Add changeset and another test
Browse files Browse the repository at this point in the history
  • Loading branch information
NullVoxPopuli committed Aug 7, 2023
1 parent ea8be9e commit 1a964f1
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 2 deletions.
133 changes: 133 additions & 0 deletions .changeset/wet-cobras-happen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
---
"ember-resources": minor
---

`trackedFunction` can now be composed just like regular resources.

The function body still auto-tracks and will update within consuming resource appropriately.

<details><summary>Example</summary>

```ts
const Person = resourceFactory((maybeIdFn) => {
return resource(({ use }) => {
let request = use(
trackedFunction(async () => {
let id = typeof maybeIdFn === "function" ? maybeIdFn() : maybeIdFn;
let response = await fetch(`https://api.github.com/users/${id}`);
return response.json();
})
);

// `use` returns a ReadonlyCell where `.current`
// is the State of trackedFunction.
return () => request.current;
});
});
```

Usage examples:

```gjs
<template>
{{#let (Person 1) as |request|}}
{{#if request.isLoading}}
... loading ...
{{/if}}
{{#if request.value}}
{{/if}}
{{/let}}
</template>
```

</details>

<details><summary>An async doubler</summary>

```ts
const Doubled = resourceFactory((num: number) => {
return resource(({ use }) => {
let doubler = use(trackedFunction(async () => num * 2));

// Since current is the "State" of `trackedFunction`,
// accessing .value on it means that the overall value of
// `Doubled` is the eventual return value of the `trackedFunction`
return () => doubler.current.value;
});
});

// Actual code from a test
class State {
@tracked num = 2;
}

let state = new State();

setOwner(state, this.owner);

await render(<template><out>{{Doubled state.num}}</out></template>);
```

</details>

<details><summary>Example with arguments</summary>

Imagine you want to compute the hypotenuse of a triangle,
but all calculations are asynchronous (maybe the measurements exist on external APIs or something).

```ts
// Actual code from a test
type NumberThunk = () => number;

const Sqrt = resourceFactory((numFn: NumberThunk) =>
trackedFunction(async () => {
let num = numFn();

return Math.sqrt(num);
})
);

const Squared = resourceFactory((numFn: NumberThunk) =>
trackedFunction(async () => {
let num = numFn();

return Math.pow(num, 2);
})
);

const Hypotenuse = resourceFactory((aFn: NumberThunk, bFn: NumberThunk) => {
return resource(({ use }) => {
const aSquared = use(Squared(aFn));
const bSquared = use(Squared(bFn));
const c = use(
Sqrt(() => {
return (aSquared.current.value ?? 0) + (bSquared.current.value ?? 0);
})
);

// We use the function return because we want this property chain
// to be what's lazily evaluated -- in this example, since
// we want to return the hypotenuse, we don't (atm)
// care about loading / error state, etc.
// In real apps, you might care about loading state though!
return () => c.current.value;

// In situations where you care about forwarding other states,
// you could do this
return {
get value() {
return c.current.value;
},
get isLoading() {
return (
a.current.isLoading || b.current.isLoading || c.current.isLoading
);
},
};
});
});
```

</details>
25 changes: 23 additions & 2 deletions test-app/tests/utils/function/rendering-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,30 @@ module('Utils | trackedFunction | rendering', function (hooks) {
assert.dom('out').hasText('4');
});

test('can be composed with the resource use', async function (assert) {
type NumberThunk = () => number;
type NumberThunk = () => number;
test('can be composed directly within a resource', async function (assert) {
const Doubled = resourceFactory((num: number) => {
return resource(({ use }) => {
let state = use(trackedFunction(() => num * 2));

return () => state.current.value;
});
});

class State {
@tracked num = 2;
}

let state = new State();

setOwner(state, this.owner);

await render(<template><out>{{Doubled state.num}}</out></template>);

assert.dom('out').hasText('4');
});

test('can be composed with the resource use', async function (assert) {
const Sqrt = resourceFactory((numFn: NumberThunk) =>
trackedFunction(async () => {
let num = numFn();
Expand Down

0 comments on commit 1a964f1

Please sign in to comment.