Skip to content

Setting tracked state inside a resource #707

@swastik

Description

@swastik

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! 😄

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions