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

Setting tracked state inside a resource

Open swastik opened this issue 2 years ago • 3 comments

Hi there! I have some code like this (simplified):

import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { useResource } from 'ember-resources';
import { action } from '@ember/object';
import { trackedFunction } from 'ember-resources/util/function';

let loadUsers = () => {
  return new Promise((resolve) => {
    setTimeout(() => resolve([1,2,3]), 1000);
  });
}

export default class UserList extends Component {
  @tracked isLoading = false;

  list = trackedFunction(this, async () => {
    this.isLoading = true;
    await loadUsers();
  });
}

… and used in a template like this:

{{#if this.isLoading}}
  Loading
{{else}}
  {{#each this.list as |user|}}
    {{user}}
  {{/each}}
{{/if}}

This throws a you-cannot-update-state-that-you-already-read error:

ic 2022-12-16 at 15 14 25


It makes sense to me given what I understand about ember's rendering: I've read isLoading in the if and then the trackedFunction is trying to update it. Apparently this style of code does not work with tasks (e.g. trackedTask either) — it results in a similar error when using the task's isRunning state.

While the error makes sense, it also seems confusing. I can think of a few approaches to handle this, e.g. a utility like a RemoteData that can wrap loading state inside the resource itself, so that the isLoading state isn't mutated in the same cycle, only after the promise resolves, e.g. (also simplified)

class State<T> {
  @tracked value?: T;
  @tracked isLoading: boolean;
}

export function AsyncData<T>(fn: (opts: { signal: AbortSignal }) => Promise<unknown>) {
  return resource(({ on }) => {
    let state = new State<T>();
    state.isLoading = true;
    
    fn({ signal: controller.signal })
      .then((value: T) => {
        state.value = value;
      }).finally(() => {
        state.isLoading = false;
      });

    return state;
  });
}

That said, I'm wondering if this is known / if we're doing something wrong, and if there are any other possible approaches to this. Thank you! 😄

swastik avatar Dec 16 '22 15:12 swastik