{"action":"created","issue":{"url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/90","repository_url":"https://api.github.com/repos/mobxjs/mobx-utils","labels_url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/90/labels{/name}","comments_url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/90/comments","events_url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/90/events","html_url":"https://github.com/mobxjs/mobx-utils/issues/90","id":264882836,"number":90,"title":"using fromPromise inside computed and trigger manual retry","user":{"login":"geekflyer","id":1221897,"avatar_url":"https://avatars1.githubusercontent.com/u/1221897?v=4","gravatar_id":"","url":"https://api.github.com/users/geekflyer","html_url":"https://github.com/geekflyer","followers_url":"https://api.github.com/users/geekflyer/followers","following_url":"https://api.github.com/users/geekflyer/following{/other_user}","gists_url":"https://api.github.com/users/geekflyer/gists{/gist_id}","starred_url":"https://api.github.com/users/geekflyer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geekflyer/subscriptions","organizations_url":"https://api.github.com/users/geekflyer/orgs","repos_url":"https://api.github.com/users/geekflyer/repos","events_url":"https://api.github.com/users/geekflyer/events{/privacy}","received_events_url":"https://api.github.com/users/geekflyer/received_events","type":"User","site_admin":false},"labels":[],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":3,"created_at":"2017-10-12T10:01:47Z","updated_at":"2017-12-06T13:18:20Z","closed_at":"2017-11-23T14:10:58Z","author_association":"NONE","body":"Hi,\r\n\r\nI found it a very nice pattern to combine `@computed` with `fromPromise` to achieve lazy data fetching. Data is fetched only once it's needed somewhere in a visible UI component (without explicitly triggering the data fetching via some router or component hooks) and automatically reloads the data once some fetching relevant param changed.\r\n\r\nFor example see the small example below:\r\n\r\n```tsx\r\nclass TodoStore {\r\n  constructor(private _accountStore: AccountStore) {}\r\n  @computed\r\n  get todosPromised() {\r\n    return fromPromise(fetch(`/${this._accountStore.currentAccountId}/todos`));\r\n  }\r\n}\r\n\r\nconst TodoView = ({todoStore}:{todoStore: TodoStore}) => todoStore.todosPromised.case({\r\n  pending: () => <div>loading...</div>,\r\n  rejected: error => <div>Error...</div>, \r\n  fulfilled: todos => renderTodos(todos)\r\n});\r\n```\r\n\r\nIn this case the data fetching gets only triggered once the `todosPromised` property is used somewhere in a component and this component is mounted / visible.\r\nAlso the computed function will automatically refetch data if the `currentAccountId` changes.\r\n\r\nNow the open question I have is how do I trigger a manual re-evaluation of the computed function (e.g. the user clicks a refresh button). This is useful if the data hasn't been refreshed for a while or if a previous fetch attempt led to an error.\r\n\r\nI was playing a bit around and found for example the following as \"solution\" even though it's a bit ugly:\r\n\r\n```tsx\r\nclass TodoStore {\r\n  constructor(private _accountStore: AccountStore) {}\r\n  \r\n  @observable\r\n  retryRequests = 0;\r\n  \r\n  @action.bound\r\n  retry(){\r\n    this.retryRequests++;\r\n  }\r\n  \r\n  @computed\r\n  get todosPromised() {\r\n    this.retryRequests;\r\n    return fromPromise(fetch(`/${this._accountStore.currentAccountId}/todos`));\r\n  }\r\n}\r\n\r\nconst TodoView = ({ todoStore }: { todoStore: TodoStore }) => todoStore.todosPromised.case({\r\n  pending: () => <div>loading...</div>,\r\n  rejected: error => <div>Error...<Button onClick={todoStore.retry}>Retry</Button></div>,\r\n  fulfilled: todos => renderTodos(todos)\r\n});\r\n```\r\n\r\nYou can see that I'm using the `retryRequests` observable prop as a dummy trigger.\r\nSince this solution is kinda ugly I tried to extend the `fromPromise` utility so that it exposes a `retry` method that can be used to trigger a follow up reaction which creates a new Promise etc..\r\nTo trigger the follow up reaction I'm using an Atom.\r\n\r\nIt looks the following:\r\n\r\n```tsx\r\nexport type IPromiseBasedObservableWithRetry<T> = IPromiseBasedObservable<T> & {\r\n  retry(): Promise<T>;\r\n}\r\n\r\nexport function fromPromiseWithRetry<T>(promise: PromiseLike<T>):  IPromiseBasedObservableWithRetry<T> {\r\n\r\n  const promiseBasedObservable = fromPromise(promise);\r\n\r\n  const retryAtom = new Atom('Retry');\r\n  retryAtom.reportObserved();\r\n  (promiseBasedObservable as any).retry = () => retryAtom.reportChanged();\r\n\r\n  return promiseBasedObservable as any;\r\n}\r\n\r\n\r\nclass TodoStore {\r\n  constructor(private _accountStore: AccountStore) {}\r\n  \r\n  @computed\r\n  get todosPromised() {\r\n    return fromPromiseWithRetry(fetch(`/${this._accountStore.currentAccountId}/todos`));\r\n  }\r\n}\r\n\r\nconst TodoView = ({ todoStore }: { todoStore: TodoStore }) => todoStore.todosPromised.case({\r\n  pending: () => <div>loading...</div>,\r\n  rejected: error => <div>Error...<Button onClick={todoStore.todosPromised.retry}>Retry</Button></div>,\r\n  fulfilled: todos => renderTodos(todos)\r\n});\r\n\r\n```\r\n\r\n**This actually works, but I'm wondering if this is advisable from a performance and (possible memory leak) perspective**. In particular I'm a bit wondering what impact it has that I'm creating a new `Atom` on every `fromPromiseWithRetry` invocation (during a reaction) and immediately call the `reportObserved`. Any thoughts @mweststrate  and whether this is good or no and what may be a better solution?\r\n\r\nbtw in part this is related to https://github.com/mobxjs/mobx/issues/307\r\n\r\nThanks and Best Regards Christian"},"comment":{"url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/comments/349636923","html_url":"https://github.com/mobxjs/mobx-utils/issues/90#issuecomment-349636923","issue_url":"https://api.github.com/repos/mobxjs/mobx-utils/issues/90","id":349636923,"user":{"login":"geekflyer","id":1221897,"avatar_url":"https://avatars1.githubusercontent.com/u/1221897?v=4","gravatar_id":"","url":"https://api.github.com/users/geekflyer","html_url":"https://github.com/geekflyer","followers_url":"https://api.github.com/users/geekflyer/followers","following_url":"https://api.github.com/users/geekflyer/following{/other_user}","gists_url":"https://api.github.com/users/geekflyer/gists{/gist_id}","starred_url":"https://api.github.com/users/geekflyer/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/geekflyer/subscriptions","organizations_url":"https://api.github.com/users/geekflyer/orgs","repos_url":"https://api.github.com/users/geekflyer/repos","events_url":"https://api.github.com/users/geekflyer/events{/privacy}","received_events_url":"https://api.github.com/users/geekflyer/received_events","type":"User","site_admin":false},"created_at":"2017-12-06T13:18:20Z","updated_at":"2017-12-06T13:18:20Z","author_association":"NONE","body":"I haven't tried @mayorovp' solution but he performs a side effect / mutation of an external observable inside a computed method which supposed to be side-effect free, which looks odd to me.\r\n\r\nAnyways, I actually built in the meantime a custom implementation of the whole thing which more or less takes the API of `fromPromise` and but in principle works like the library https://github.com/danielearwicker/computed-async-mobx/issues/13 (computed-async-mobx can do the same thing but I didn't like it's API that much).\r\n\r\nHere's my implementation:\r\n\r\n```ts\r\nimport { action, computed, observable } from 'mobx';\r\nimport { fromPromise, IPromiseBasedObservable, FULFILLED, PENDING, REJECTED } from 'mobx-utils';\r\n\r\nexport type IBaseAsyncComputed<T> = {\r\n  refresh(): void;\r\n  case<U>(handlers: { pending?: () => U; fulfilled?: (t: T) => U; rejected?: (e: any) => U }): U;\r\n};\r\n\r\nexport interface IPendingAsyncComputed<T> extends IBaseAsyncComputed<T> {\r\n  readonly state: 'pending';\r\n  readonly pending: true;\r\n  readonly rejected: false;\r\n  readonly fulfilled: false;\r\n}\r\n\r\nexport interface IRejectedAsyncComputed<T> extends IBaseAsyncComputed<T> {\r\n  readonly state: 'rejected';\r\n  readonly pending: false;\r\n  readonly rejected: true;\r\n  readonly fulfilled: false;\r\n  readonly error: any;\r\n}\r\n\r\nexport interface IFulfilledAsyncComputed<T> extends IBaseAsyncComputed<T> {\r\n  readonly state: 'fulfilled';\r\n  readonly pending: false;\r\n  readonly rejected: false;\r\n  readonly fulfilled: true;\r\n  readonly value: T;\r\n}\r\n\r\nexport type IAsyncComputed<T> = IPendingAsyncComputed<T> | IFulfilledAsyncComputed<T> | IRejectedAsyncComputed<T>;\r\n\r\nclass AsyncComputed<T> {\r\n  @observable private refreshCallCount = 0;\r\n\r\n  @computed\r\n  get state() {\r\n    return this._internalObservable.state;\r\n  }\r\n\r\n  constructor(private readonly computeFn: () => PromiseLike<T>) {}\r\n\r\n  @computed\r\n  get pending() {\r\n    return this.state === PENDING;\r\n  }\r\n\r\n  @computed\r\n  get rejected() {\r\n    return this.state === REJECTED;\r\n  }\r\n\r\n  @computed\r\n  get fulfilled() {\r\n    return this.state === FULFILLED;\r\n  }\r\n\r\n  @computed\r\n  get value() {\r\n    if (this._internalObservable.state === FULFILLED) {\r\n      return this._internalObservable.value;\r\n    } else {\r\n      return undefined;\r\n    }\r\n  }\r\n\r\n  @computed\r\n  get error() {\r\n    if (this._internalObservable.state === REJECTED) {\r\n      return this._internalObservable.value;\r\n    } else {\r\n      return undefined;\r\n    }\r\n  }\r\n\r\n  @action.bound\r\n  refresh() {\r\n    this.refreshCallCount++;\r\n  }\r\n\r\n  @computed\r\n  get case() {\r\n    return this._internalObservable.case;\r\n  }\r\n\r\n  @computed\r\n  private get _internalObservable(): IPromiseBasedObservable<T> {\r\n    this.refreshCallCount;\r\n    const observablePromise = fromPromise(this.computeFn());\r\n    // handle rejections etc. because they'll otherwise bubble up and crash node.js\r\n    (observablePromise as any).catch(e => undefined);\r\n    return observablePromise;\r\n  }\r\n}\r\n\r\nexport default function asyncComputed<T>(computeFn: () => PromiseLike<T>) {\r\n  return new AsyncComputed(computeFn) as IAsyncComputed<T>;\r\n}\r\n```\r\n\r\nusage example:\r\n```ts\r\n\r\nclass ThingStore {\r\n\r\n@observable thingIdToDisplay = 0;\r\n\r\nthing = asyncComputed(() => fetchStuff('./foo/bar/${this.thingIdToDisplay}')\r\n\r\n}\r\n\r\n\r\n// and somewhere in your event handler when a user clicks the `refresh` button manually:\r\nthingStore.thing.refresh();\r\n\r\n```"}}
{"actor":{"display_login":"geekflyer"}}