using fromPromise inside computed and trigger manual retry
See original GitHub issueHi,
I 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.
For example see the small example below:
class TodoStore {
constructor(private _accountStore: AccountStore) {}
@computed
get todosPromised() {
return fromPromise(fetch(`/${this._accountStore.currentAccountId}/todos`));
}
}
const TodoView = ({todoStore}:{todoStore: TodoStore}) => todoStore.todosPromised.case({
pending: () => <div>loading...</div>,
rejected: error => <div>Error...</div>,
fulfilled: todos => renderTodos(todos)
});
In this case the data fetching gets only triggered once the todosPromised
property is used somewhere in a component and this component is mounted / visible.
Also the computed function will automatically refetch data if the currentAccountId
changes.
Now 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.
I was playing a bit around and found for example the following as “solution” even though it’s a bit ugly:
class TodoStore {
constructor(private _accountStore: AccountStore) {}
@observable
retryRequests = 0;
@action.bound
retry(){
this.retryRequests++;
}
@computed
get todosPromised() {
this.retryRequests;
return fromPromise(fetch(`/${this._accountStore.currentAccountId}/todos`));
}
}
const TodoView = ({ todoStore }: { todoStore: TodoStore }) => todoStore.todosPromised.case({
pending: () => <div>loading...</div>,
rejected: error => <div>Error...<Button onClick={todoStore.retry}>Retry</Button></div>,
fulfilled: todos => renderTodos(todos)
});
You can see that I’m using the retryRequests
observable prop as a dummy trigger.
Since 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…
To trigger the follow up reaction I’m using an Atom.
It looks the following:
export type IPromiseBasedObservableWithRetry<T> = IPromiseBasedObservable<T> & {
retry(): Promise<T>;
}
export function fromPromiseWithRetry<T>(promise: PromiseLike<T>): IPromiseBasedObservableWithRetry<T> {
const promiseBasedObservable = fromPromise(promise);
const retryAtom = new Atom('Retry');
retryAtom.reportObserved();
(promiseBasedObservable as any).retry = () => retryAtom.reportChanged();
return promiseBasedObservable as any;
}
class TodoStore {
constructor(private _accountStore: AccountStore) {}
@computed
get todosPromised() {
return fromPromiseWithRetry(fetch(`/${this._accountStore.currentAccountId}/todos`));
}
}
const TodoView = ({ todoStore }: { todoStore: TodoStore }) => todoStore.todosPromised.case({
pending: () => <div>loading...</div>,
rejected: error => <div>Error...<Button onClick={todoStore.todosPromised.retry}>Retry</Button></div>,
fulfilled: todos => renderTodos(todos)
});
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?
btw in part this is related to https://github.com/mobxjs/mobx/issues/307
Thanks and Best Regards Christian
Issue Analytics
- State:
- Created 6 years ago
- Comments:11 (1 by maintainers)
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.
Anyways, I actually built in the meantime a custom implementation of the whole thing which more or less takes the API of
fromPromise
and augments it withrefresh
and a few other methods. In principle works under the hood like the library https://github.com/danielearwicker/computed-async-mobx/issues/13 but instead of having a complete custom API it stays consistent with fromPromise wherever possible.Here’s my implementation:
usage example:
Let me know if you find that useful - I can publish this implementation on npm if it is.
@dbrody Unfortunately not. I’ve created a github repo just now though to publish the internal version of this utility: https://github.com/solvvy/mobx-async-computed . Since it’s really just 1 file which contains all the code you could technically simply copy it into your project to try it out: https://github.com/solvvy/mobx-async-computed/blob/master/asyncComputed.ts . Let me know if you find that useful. If so I may be more motivated to actually publish it on npm 😉
If you don’t use typescript here’s the pre-compiled code: