ember-resources icon indicating copy to clipboard operation
ember-resources copied to clipboard

Persisted state across argument changes

Open scottmessinger opened this issue 1 year ago • 1 comments

Hi! We're looking at upgrading from can-get-used-to-this and was wondering about the issue of persisting state across argument changes. According to the class based resource library, persisted state isn't natively supported by a function approach.

Just to make sure I understand, this means that the output will change it's identify in JS (e.g. a new object is created) when arguments change. If I'm right, that's a massive performance issue as a resource is often used to return a model which anchors the entire UI. Changing an argument would cause the entire UI to rerender. This seems like such an problematic choice, I'm assuming I must be misunderstanding it or I'm just unaware of a relatively easy and accessible solution.

Could you provide any insight on this? How can the function approach to resources return stable objects as the arguments change? If that's not possible, how are the performance implications mitigated?

Thank you!

scottmessinger avatar Dec 13 '24 16:12 scottmessinger

What sort of public API are you working with?

Resources are a primitive for managing "a thing with cleanup", and anything beyond that is kind of an exercise in delaying tracked-entanglement to the user.

For example, in https://github.com/NullVoxPopuli/ember-resources/issues/1056, we go over a few ways of achieving similar results to "the old way of doing things", but in terms of the new apis.

Like, if you want to return a stable object as arguments change -- we first need to define what it means for arguments to change.

If we can pass a function, or object of getters, we can use an approach like this to achieve a stable return object:

import { use, resource } from 'ember-resources';

class MyDoubler {
    constructor(inputFn) { this.inputFn = inputFn; }
    
    get num() {
      return this.inputFn() * 2;
    }
    
    // not required, if you don't want
    destroy() {}
}

function Doubler(inputFn) {
  let state = new MyDoubler(inputFn);
  
  return resource(({ on, owner }) => {
    setOwner(state, owner);
    
    // not required if you don't want
    on.cleanup(() => state.destroy());

    return state;
  });
}

class Demo {
  @tracked something = 3;
    
  @use doubler = Doubler(() => this.something);
  
  get theValue() {
    return this.doubler.num; // 6
  }
}

at the same time, https://github.com/NullVoxPopuli/ember-modify-based-class-resource/ is also built on stable, public APIs, and has no intention of being abondoned or anything like that and provides the behavior you may be used to (with the modify hook).

For more philosophy reasons for why we don't, in general, want a modify hook, see here: https://github.com/NullVoxPopuli/ember-resources/issues/1054#issuecomment-1858492428

Now, something that is important is that resources aren't a hammer that we use for every nail -- resources are more of a specific screwdriver for a specific set of screws.

Many folks can get away with these sorts of stable references (like if they don't need per-arg-change-cleanup):

class MyClass {
  constructor(fooFn) {
    this.#fooFn = fooFn;
  }

  get foo() {
    return this.#fooFn();
  }
}

class Demo {
  // stable!
  myInstance = new MyClass(() => this.args.foo);
}

and if you need ownership / destruction linking, you may be interested in https://reactive.nullvoxpopuli.com/functions/link.link.html

NullVoxPopuli avatar Dec 13 '24 16:12 NullVoxPopuli