Options
All
  • Public
  • Public/Protected
  • All
Menu

Index

References

Renames and re-exports helper

Type aliases

Thunk: (() => []) | (() => void) | (() => Required<ArgsWrapper>["positional"]) | (() => Required<ArgsWrapper>["named"]) | (() => ArgsWrapper)

With the exception of the useResource + class combination, all Thunks are optional. The main caveat is that if your resources will not update without a thunk -- or consuming tracked data within setup / initialization (which is done for you with useFunction).

  • The thunk is "just a function" that allows tracked data to be lazily consumed by the resource.

Note that thunks are awkward when they aren't required -- they may even be awkward when they are required. Whenever possible, we should rely on auto-tracking, such as what trackedFunction provides.

So when and why are thunks needed?

  • when we want to manage reactivity separately from a calling context.
  • in many cases, the thunk is invoked during setup and update of various Resources, so that the setup and update evaluations can "entangle" with any tracked properties accessed within the thunk. This allows changes to those tracked properties to cause the Resources to (re)update.

The args thunk accepts the following data shapes:

() => [an, array]
() => ({ hello: 'there' })
() => ({ named: {...}, positional: [...] })

An array

when an array is passed, inside the Resource, this.args.named will be empty and this.args.positional will contain the result of the thunk.

for function resources, this is the only type of thunk allowed.

An object of named args

when an object is passed where the key named is not present, this.args.named will contain the result of the thunk and this.args.positional will be empty.

An object containing both named args and positional args

when an object is passed containing either keys: named or positional:

  • this.args.named will be the value of the result of the thunk's named property
  • this.args.positional will be the value of the result of the thunk's positional property

This is the same shape of args used throughout Ember's Helpers, Modifiers, etc

Functions

  • trackedFunction<Return>(...passed: Vanilla<Return>): { value: Return }
  • trackedFunction<Return>(...passed: WithInitialValue<Return>): { value: Return }
  • For use in the body of a class. Constructs a cached Resource that will reactively respond to tracked data changes

    Type parameters

    • Return

    Parameters

    • Rest ...passed: Vanilla<Return>

    Returns { value: Return }

    • value: Return
  • For use in the body of a class. Constructs a cached Resource that will reactively respond to tracked data changes

    Type parameters

    • Return

    Parameters

    • Rest ...passed: WithInitialValue<Return>

    Returns { value: Return }

    • value: Return
  • use(_prototype: object, key: string, descriptor?: Descriptor): void
  • deprecated
    decorator

    @use


    The @use decorator abstracts away the underlying reactivity configuration needed to use a Resource. @use can work with Resource or LifecycleResource.

    example
    import { use } from 'ember-resources';
    import { SomeResource } from './some-resource';

    class MyClass {
    @use data = SomeResource.with(() => [arg list]);
    }

    All subclasses of Resource and LifecycleResource have a static method, with. This with method takes the same argument Thunk you'll see throughout other usages of Resources in this document.

    The type of data in this example will be an instance of SomeResource, so that typescript is happy / correct.

    Parameters

    • _prototype: object
    • key: string
    • Optional descriptor: Descriptor

    Returns void

  • useFunction<Return, Args>(...passed: NonReactiveVanilla<Return, Args>): { value: Return }
  • useFunction<Return, Args>(...passed: VanillaArgs<Return, Args>): { value: Return }
  • useFunction<Return, Args>(...passed: WithInitialValueArgs<Return, Args>): { value: Return }
  • useFunction<Return, Args>(...passed: NonReactiveWithInitialValue<Return, Args>): { value: Return }
  • deprecated

    use trackedFunction instead

    description

    useFunction provides a way reactively call a function when args to that function change. For use in the body of a class.

    example
    import { useFunction } from 'ember-resources';

    class StarWarsInfo {
    // access result on info.value
    info = useFunction(this, async (state, ...args) => {
    if (state) {
    let { characters } = state;

    return { characters };
    }

    let [ids] = args;
    let response = await fetch(`/characters/${ids}`) ;
    let characters = await response.json();

    return { characters };
    }, () => [this.ids]) // this.ids defined somewhere
    }

    characters would be accessed via this.info.value.characters in the StarWarsInfo class

    While this example is a bit contrived, hopefully it demonstrates how the state arg works. During the first invocation, state is falsey, allowing the rest of the function to execute. The next time this.ids changes, the function will be called again, except state will be the { characters } value during the first invocation, and the function will return the initial data.

    This particular technique could be used to run any async function safely (as long as the function doesn't interact with this).

    In this example, where the function is async, the "value" of info.value is undefined until the function completes.

    To help prevent accidental async footguns, even if a function is synchronous, it is still ran asynchronously, therefor, the thunk cannot be avoided.

    import { useFunction } from 'ember-resources';

    class MyClass {
    @tracked num = 3;

    info = useFunction(this, () => {
    return this.num * 2;
    });
    }

    this.info.value will be undefined, then 6 and will not change when num changes.

    example

    These patterns are primarily unexplored so if you run in to any issues, please open a bug report / issue.

    Composing class-based resources is expected to "just work", as classes maintain their own state.

    useFunction + useFunction

    import Component from '@glimmer/component';
    import { useFunction } from 'ember-resources';

    class MyComponent extends Component {
    rand = useFunction(this, () => {
    return useFunction(this, () => Math.random());
    });
    }

    Accessing the result of Math.random() would be done via:

    {{this.rand.value.value}}
    

    Something to note about composing resources is that if arguments passed to the outer resource change, the inner resources are discarded entirely.

    For example, you'll need to manage the inner resource's cache invalidation yourself if you want the inner resource's behavior to be reactive based on outer arguments:

    Example data fetching composed functions
    import Component from '@glimmer/component';
    import { useFunction } from 'ember-resources';

    class MyComponent extends Component {
    @tracked id = 1;
    @tracked storeName = 'blogs';

    records = useFunction(this, (state, storeName) => {
    let result: Array<string | undefined> = [];

    if (state?.previous?.storeName === storeName) {
    return state.previous.innerFunction;
    }

    let innerFunction = useFunction(this, (prev, id) => {
    // pretend we fetched a record using the store service
    let newValue = `record:${storeName}-${id}`;

    result = [...(prev || []), newValue];

    return result;
    },
    () => [this.id]
    );

    return new Proxy(innerFunction, {
    get(target, key, receiver) {
    if (key === 'previous') {
    return {
    innerFunction,
    storeName,
    };
    }

    return Reflect.get(target, key, receiver);
    },
    });
    },
    () => [this.storeName]
    );
    }
    {{this.records.value.value}} -- an array of "records"
    

    Type parameters

    • Return

    • Args: unknown[] = unknown[]

    Parameters

    • Rest ...passed: NonReactiveVanilla<Return, Args>

    Returns { value: Return }

    • value: Return
  • Type parameters

    • Return

    • Args: unknown[] = unknown[]

    Parameters

    • Rest ...passed: VanillaArgs<Return, Args>

    Returns { value: Return }

    • value: Return
  • Type parameters

    • Return

    • Args: unknown[] = unknown[]

    Parameters

    • Rest ...passed: WithInitialValueArgs<Return, Args>

    Returns { value: Return }

    • value: Return
  • Type parameters

    • Return

    • Args: unknown[] = unknown[]

    Parameters

    • Rest ...passed: NonReactiveWithInitialValue<Return, Args>

    Returns { value: Return }

    • value: Return
  • useResource<Instance>(context: object, klass: Constructable<Instance>, thunk?: Thunk): Instance
  • useResource<Instance>(context: object, klass: Constructable<Instance>, thunk?: Thunk): Instance
  • deprecated

    For use in the body of a class.

    useResource takes either a Resource or LifecycleResource and an args Thunk.

    useResource is what allows Resources to be used in JS, they hide the reactivity APIs from the consumer so that the surface API is smaller. Though, from an end-user-api ergonomics perspective, you wouldn't typically want to rely on this. As in ember-data-resources the useResource + Resource class are coupled together in to more meaningful APIs -- allowing only a single import in most cases.

    import { useResource } from 'ember-resources';

    class MyClass {
    data = useResource(this, SomeResource, () => [arg list]);
    }

    When any tracked data in the args thunk is updated, the Resource will be updated as well

    • The this is to keep track of destruction -- so when MyClass is destroyed, all the resources attached to it can also be destroyed.

    • The resource will do nothing until it is accessed. Meaning, if you have a template that guards access to the data, like:

      {{#if this.isModalShowing}}
      <Modal>{{this.data.someProperty}}</Modal>
      {{/if}}

      the Resource will not be instantiated until isModalShowing is true.

    • For more info on Thunks, scroll to the bottom of the README

    Type parameters

    Parameters

    • context: object
    • klass: Constructable<Instance>
    • Optional thunk: Thunk

    Returns Instance

  • deprecated

    For use in the body of a class. Constructs a cached Resource that will reactively respond to tracked data changes

    Type parameters

    Parameters

    • context: object
    • klass: Constructable<Instance>
    • Optional thunk: Thunk

    Returns Instance

  • useTask<Return, Args, LocalTask>(context: object, task: LocalTask, thunk?: () => Args): TaskInstance<Return>
  • deprecated
    utility

    uses LifecycleResource to make ember-concurrency tasks reactive.


    note

    ember-resources does not provide or depend on ember-concurrency. If you want to use useTask, you'll need to add ember-concurrency as a dependency in your project.

    example

    When this.id changes, the task will automatically be re-invoked.

    import { tracked } from '@glimmer/tracking';
    import { task, timeout } from 'ember-concurrency';
    import { useTask } from 'ember-resources';

    class Demo {
    @tracked id = 1;

    last = useTask(this, this.searchTask, () => [this.id]);

    @task
    *searchTask(id) {
    yield timeout(200);
    yield fetch('...');

    return 'the-value';
    }
    }
    Available Properties:
    {{this.last.value}}
    {{this.last.isFinished}}
    {{this.last.isRunning}}
    {{this.last.value}}

    (and all other properties on a TaskInstance)

    Type parameters

    • Return = unknown

    • Args: unknown[] = unknown[]

    • LocalTask: TaskIsh<Args, Return, LocalTask> = TaskIsh<Args, Return>

    Parameters

    • context: object
    • task: LocalTask
    • Optional thunk: () => Args
        • (): Args
        • Returns Args

    Returns TaskInstance<Return>

Generated using TypeDoc