Code Monkey home page Code Monkey logo

computed-async-mobx's Introduction

computed-async-mobx

Define a computed by returning a Promise

Build Status Coverage Status

"People starting with MobX tend to use reactions [autorun] too often. The golden rule is: if you want to create a value based on the current state, use computed." - MobX - Concepts & Principles

About MobX 6

An attempt was made, then abandoned, to quick-fix this library to work with MobX 6, but it didn't work out (to put it mildly). Any suggestions for how to fix it are welcome! For now it only works with MobX versions 3.0 to 5.x.

It's possible the API may need to be cut down to something a bit simpler with fewer guarantees but still achieving the basic goal.

What is this for?

A computed in MobX is defined by a function, which consumes other observable values and is automatically re-evaluated, like a spreadsheet cell containing a calculation.

@computed get creditScore() {
    return this.scoresByUser[this.userName];
}

However, it has to be a synchronous function body. What if you want to do something asynchronous? e.g. get something from the server. That's where this little extension comes in:

creditScore = promisedComputed(0, async () => {
    const response = await fetch(`users/${this.userName}/score`);
    const data = await response.json();
    return data.score;
});

Further explanation, rationale, etc.

New in Version 3.0.0...

There is a completely new API, much more modular and made of simple, testable pieces. The old API is deprecated, though is still available for now. First here's how the current features work. Stay tuned for a migration guide below.


asyncComputed

This is the most capable function. It is actually just a composition of two simpler functions, promisedComputed and throttledComputed, described below.

Parameters

  • init - the value to assume until the first genuine value is returned
  • delay - the minimum time in milliseconds to wait between creating new promises
  • compute - the function to evaluate to get a promise (or plain value)

Returns

A Mobx-style getter, i.e. an object with a get function that returns the current value. It is an observable, so it can be used from other MobX contexts. It cannot be used outside MobX reactive contexts (it throws an exception if you attempt it).

The returned object also has a busy property that is true while a promise is still pending. It also has a refresh method that can be called to force a new promise to be requested immediately (bypassing the delay time).

New in 4.2.0: there is also a method getNonReactive() which can be used outside reactive contexts. It is a convenience for writing unit tests. Note that it will return the most recent value that was computed while the asyncComputed was being observed.

Example

fullName = asyncComputed("(Please wait...)", 500, async () => {
        const response = await fetch(`users/${this.userName}/info`);
        const data = await response.json();
        return data.fullName;
});

The value of fullName.get() is observable. It will initially return "(Please wait...)" and will later transition to the user's full name. If the this.userName property is an observable and is modified, the promisedComputed will update also, but after waiting at least 500 milliseconds.


promisedComputed

Like asyncComputed but without the delay support. This has the slight advantage of being fully synchronous if the compute function returns a plain value.

Parameters

  • init - the value to assume until the first genuine value is returned
  • compute - the function to evaluate to get a promise (or plain value)

Returns

Exactly as asyncComputed.

Example

fullName = promisedComputed("(Please wait...)", async () => {
    const response = await fetch(`users/${this.userName}/info`);
    const data = await response.json();
    return data.fullName;
});

The value of fullName.get() is observable. It will initially return "(Please wait...)" and will later transition to the user's full name. If the this.userName property is an observable and is modified, the promisedComputed will update also, as soon as possible.


throttledComputed

Like the standard computed but with support for delaying for a specified number of milliseconds before re-evaluation. It is like a computed version of the standard autorunAsync; the advantage is that you don't have to manually dispose it.

(Note that throttledComputed has no special functionality for handling promises.)

Parameters

  • compute - the function to evaluate to get a plain value
  • delay - the minimum time in milliseconds to wait before re-evaluating

Returns

A Mobx-style getter, i.e. an object with a get function that returns the current value. It is an observable, so it can be used from other MobX contexts. It can also be used outside MobX reactive contexts but (like standard computed) it reverts to simply re-evaluating every time you request the value.

It also has a refresh method that immediately (synchronously) re-evaluates the function.

Example

fullName = throttledComputed(500, () => {
    const data = slowSearchInMemory(this.userName);
    return data.fullName;
});

The value of fullName.get() is observable. It will initially return the result of the search, which happens synchronously the first time. If the this.userName property is an observable and is modified, the throttledComputed will update also, but after waiting at least 500 milliseconds.


autorunThrottled

Much like the standard autorunAsync, except that the initial run of the function happens synchronously.

(This is used by throttledComputed to allow it to be synchronously initialized.)

Parameters

  • func - The function to execute in reaction
  • delay - The minimum delay between executions
  • name - (optional) For MobX debug purposes

Returns

  • a disposal function.

A Mobx-style getter, i.e. an object with a get function that returns the current value. It is an observable, so it can be used from other MobX contexts. It can also be used outside MobX reactive contexts but (like standard computed) it reverts to simply re-evaluating every time you request the value.


Installation

npm install computed-async-mobx

TypeScript

Of course TypeScript is optional; like a lot of libraries these days, this is a JavaScript library that happens to be written in TypeScript. It also has built-in type definitions: no need to npm install @types/... anything.

Acknowledgements

I first saw this idea on the Knockout.js wiki in 2011. As discussed here it was tricky to make it well-behaved re: memory leaks for a few years.

MobX uses the same (i.e. correct) approach as ko.pureComputed from the ground up, and the Atom class makes it easy to detect when your data transitions between being observed and not. More recently I realised fromPromise in mobx-utils could be used to implement promisedComputed pretty directly. If you don't need throttling (delay parameter) then all you need is a super-thin layer over existing libraries, which is what promisedComputed is.

Also a ๐ŸŒน for Basarat for pointing out the need to support strict mode!

Thanks to Daniel Nakov for fixes to support for MobX 4.x.

Usage

Unlike the normal computed feature, promisedComputed can't work as a decorator on a property getter. This is because it changes the type of the return value from PromiseLike<T> to T.

Instead, as in the example above, declare an ordinary property. If you're using TypeScript (or an ES6 transpiler with equivalent support for classes) then you can declare and initialise the property in a class in one statement:

class Person {

     @observable userName: string;

     creditScore = promisedComputed(0, async () => {
         const response = await fetch(`users/${this.userName}/score`);
         const data = await response.json();
         return data.score; // score between 0 and 1000
     });

     @computed
     get percentage() {
         return Math.round(this.creditScore.get() / 10);
     }
}

Note how we can consume the value via the .get() function inside another (ordinary) computed and it too will re-evaluate when the score updates.

{ enforceActions: "always" }

This library is transparent with respect to MobX's strict mode, and since 4.2.0 this is true even of the very strict "always" mode that doesn't even let you initialize fields of a class outside a reactive context.

Gotchas

Take care when using async/await. MobX dependency tracking can only detect you reading data in the first "chunk" of a function containing awaits. It's okay to read data in the expression passed to await (as in the above example) because that is evaluated before being passed to the first await. But after execution "returns" from the first await the context is different and MobX doesn't track further reads.

For example, here we fetch two pieces of data to combine them together:

answer = asyncComputed(0, 1000, async () => {
    const part1 = await fetch(this.part1Uri),
          part2 = await fetch(this.part2Uri);
    
    // combine part1 and part2 into a result somehow...
    return result;
});

The properties part1Uri and part2Uri are ordinary mobx observables (or computeds). You'd expect that when either of those values changes, this asyncComputed will re-execute. But in fact it can only detect when part1Uri changes. When an async function is called, only the first part (up to the first await) executes immediately, and so that's the only part that MobX will be able to track. The remaining parts execute later on, when MobX has stopped listening.

(Note: the expression on the right of await has to be executed before the await pauses the function, so the access to this.part1Uri is properly detected by MobX).

We can work around this like so:

answer = asyncComputed(0, 1000, async () => {
    const uri1 = this.part1Uri, 
          uri2 = this.part2Uri;

    const part1 = await fetch(uri1),
          part2 = await fetch(uri2);

    // combine part1 and part2 into a result somehow...
    return result;
});

When in doubt, move all your gathering of observable values to the start of the async function.

Migration

The API of previous versions is still available. It was a single computedAsync function that had all the capabilities, like a Swiss-Army Knife, making it difficult to test, maintain and use. It also had some built-in functionality that could just as easily be provided by user code, which is pointless and only creates obscurity.

  • Instead of calling computedAsync with a zero delay, use promisedComputed, which takes no delay parameter.
  • Instead of calling computedAsync with a non-zero delay, use asyncComputed.
  • Instead of using the value property, call the get() function (this is for closer consistency with standard MobX computed.)
  • Instead of using revert, use the busy property to decide when to substitute a different value.
  • The rethrow property made computedAsync propagate exceptions. There is no need to request this behaviour with promisedComputed and asyncComputed as they always propagate exceptions.
  • The error property computed a substitute value in case of an error. Instead, just do this substitution in your compute function.

Version History

See CHANGES.md.

License

MIT, see LICENSE

computed-async-mobx's People

Contributors

amytych avatar danielearwicker avatar dnakov avatar egor-koldasov avatar fiscalbot avatar unimonkiez avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

computed-async-mobx's Issues

Error: promisedComputed must be used inside reactions

Hi! I think this is related to #12

I have this wrapper around asyncComputed for ease of use:

export const asyncComputed = (initial, fn) => {
  // wrap asyncComputed so it follows the same sort of api as fromResource
  const computed = originalAsyncComputed(initial, 1000, fn)
  return {
    busy: () => computed.busy,
    current: computed.get,
  }
}

Then I have an asyncComputed variable on the ERC20 class like so (pretty standard)

export default class ERC20 {
  // ... 
  name = asyncComputed('...', async () => {
    try {
      const name = await this.contract.methods.name().call()
      if (!name) { throw new Error() }
      return name
    } catch (error) {
      return 'Invalid Token'
    }
  })
  // ... 
}

finally, I have these set of computed functions to derive info about the canonical tokens

  @computed get canonicalTokens () {
    const networkId = this.root.web3Context.network.id
    if (!networkId) { return [] }

    const tokenArtifacts = [Test1Token, Test2Token, Test3Token]
    // this next line is the only relevant one
    return tokenArtifacts.map(ct =>
      new ERC20(ct.networks[networkId].address)
    )
  }

  @computed get canonicalTokenInfo () {
    return this.canonicalTokens.map(ct => ({
      busy: ct.name.busy() || ct.symbol.busy(),
      name: ct.name.current(),
      symbol: ct.symbol.current(),
      address: ct.address,
    }))
  }

  // are any of the canonical token things busy?
  @computed get isLoadingCanonicalTokens () {
    return this.canonicalTokens.length === 0 ||
      some(this.canonicalTokens, (ct) => ct.busy)
  }

The error occurs when accessing canonicalTokenInfo with

Unhandled Rejection (Error): promisedComputed must be used inside reactions

  110 | @computed get canonicalTokenInfo () {
  111 |   return this.canonicalTokens.map(ct => ({
  112 |     busy: ct.name.busy() || ct.symbol.busy(),
> 113 |     name: ct.name.current(),
  114 |     symbol: ct.symbol.current(),
  115 |     address: ct.address,
  116 |   }))

  108 | }
  109 | 
  110 | @computed get canonicalTokenInfo () {
> 111 |   return this.canonicalTokens.map(ct => ({
  112 |     busy: ct.name.busy() || ct.symbol.busy(),
  113 |     name: ct.name.current(),
  114 |     symbol: ct.symbol.current(),

  34 | _lazyInitForm = async () => {
  35 |   await when(() => !this.props.store.domain.isLoadingCanonicalTokens)
  36 | 
> 37 |   this.form = buildRecipeForm(this.props.store.domain.canonicalTokenInfo)
  38 | 
  39 |   // add initial input
  40 |   this._addInput()

Some questions:

  1. is that wrapper the problem? If so, how would I resolve this issue?
  2. is that isLoadingCanonicalTokens function an anti-pattern?
  3. It seems that all accessing is being done in the computed functions, which means they should be a reaction.

Any ideas?

simple use case not working

import { observable, autorun } from 'mobx';
import { promisedComputed } from 'computed-async-mobx';

class MyState {
  @observable foo = 1;
  bar = promisedComputed('loading', async () => {
    return Promise.resolve('loaded' + this.foo);
  }

  @computed
  get bar2(){
    return this.bar.get();
  }
}

let myState = new MyState();

autorun(() => {
  console.log(myState.bar); //doesnt work
});

autorun(() => {
  console.log(myState.bar2); //works
});

is there a way to use bar directily without .get() like i do with @computed?
there where no full examples in the documentation.

Missing mobx-utils dependency? Fails in jest specs?

Cannot find module 'mobx-utils' from 'promisedComputed.js'

    However, Jest was able to find:
    	'./promisedComputed.d.ts'
    	'./promisedComputed.js'
    	'./promisedComputed.js.map'

    You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx'].

had to install mobx-utils seperately

Getting Uncaught Error: [mobx] Cycle detected in computation

Hello,
I am new to MobX so it could be stupid question :)

I wrote the following code which works like a charm:

@observable time1: number | undefined
@observable near: string | undefined
@observable far: string | undefined

fixingDateNearPromise = promisedComputed('',
    async () => {
      if(!this.time1 || !this.near)
        return ''
      const request = {
        underlyingCurrency: this.near,
        valuedCurrency: this.far,
        crossCurrency: '',
        period: formatDate(this.time1)
      }
      const response = await REST.quote.getFixingDate(request)
      return response.data.valueDateAndFixingDate.fixingDate
    }
  )

@computed
  get fixingDateNear() {
    return this.fixingDateNearPromise.get()
  }

Then I tried to optimize this code, since I need two computed values that derive their value from this.time1 and this.time2:

fixingDatePromise = (time) => promisedComputed('',
    async () => {
      if(!time || !this.near)
        return ''
      const request = {
        underlyingCurrency: this.near,
        valuedCurrency: this.far,
        crossCurrency: '',
        period: formatDate(time)
      }
      const response = await REST.quote.getFixingDate(request)
      return response.data.valueDateAndFixingDate.fixingDate
    }
  )

  @computed
  get fixingDateNear() {
    return this.fixingDatePromise(this.time1).get()
  }

  @computed
  get fixingDateFar() {
    return this.fixingDatePromise(this.time2).get()
  }

The code above resulted in cycle in MobX:

Uncaught Error: [mobx] Cycle detected in computation [email protected]: function get() {
      try {
        this.refreshCallCount;
        var promiseOrValue = this.fetch();
        return isPromiseLike(promiseOrValue) ? mobx_utils_1.fromPromise(promiseOrValue.then(value, function (e) {
          return error(e);
        })) : value(promiseOrValue);
      } catch (x) {
        return error(x);
      }
    }
    at invariant (mobx.module.js?7277:90)
    at fail (mobx.module.js?7277:85)
    at ComputedValue.get (mobx.module.js?7277:879)
    at ObservableObjectAdministration.read (mobx.module.js?7277:3880)
    at PromisedComputed.get (mobx.module.js?7277:4144)
    at eval (eval at <anonymous> ($rfsq-panel.tsx?efef:67), <anonymous>:1:6)
    at PromisedComputed.eval [as fetch] ($rfsq-panel.tsx?efef:324)
    at PromisedComputed.get (promisedComputed.js?1a17:34)
    at trackDerivedFunction (mobx.module.js?7277:1154)
    at ComputedValue.computeValue (mobx.module.js?7277:946)

Could you please let me know what is wrong here?
Thanks.

Suggestion waitForAsyncComputed

Hi @danielearwicker ๐Ÿ‘‹

first of all, thanks for this amazing little library. This is exactly I was looking for :)


We've written a little helper function in order to wait for async computed properties in regular async functions while still benefiting from memoization of computed properties:

import { PromisedComputedValue } from "computed-async-mobx";
import { when } from "mobx";

export const waitForAsyncComputed = async <T>(
  asyncComputed: PromisedComputedValue<T>,
): Promise<T> => {
  let value: T;

  await when(() => {
    value = asyncComputed.get();

    return asyncComputed.busy === false;
  });

  return value!;
};

What do you think? Is this good or kind of an anti-pattern? ๐Ÿ˜ Would you be interested in adding this to the library?

Should depending on .busy keep autorun alive?

One weird and common behavior that I've noticed is this (where store.query is a computedAsync):

const Comp = observer(({ store }) => (
  store.query.busy
    ? <div>Loading...</div>
    : <div>Results are loaded: {query.value}</div>}
))

The problem is that query.busy gets set to true, and then the component technically stops observing query.value, so the autorun is cancelled and the promise is never resolved. An example fiddle is here: https://jsfiddle.net/qc26pb4k/6/.

A fix is simply to have the component explicitly depend on query.value, even while query.busy is true. See line 136 in the fiddle for an example of that fix.

Maybe depending on busy should automatically indicate that the component depends on value? Let me know your thoughts.

Mobx 6 support

Hi, we're using computed-async-mobx (thanks for it!) and recently when trying to update to Mobx6 we encountered an error.

This prompted me to look into this repo and I see it's been a while since it was last updated, I wonder whether it's still maintained and if there are any plans to make it work with Mobx6?

Thanks!

Usage with mobx-state-tree?

I understand how to use this library with mobx but I can't seem to find examples on how to use it with mobx-state-tree? Any thoughts?

Throttling observable

@observable values
throttledValues = throttledComputed(() => {
  return this.values
}, 2000)

When changing values, throttledValues changes immediately. This function is actually run only in the beginning. Is it the intended behavior ?
If so, how my intention can still be implemented?

delay not working in 2.0.0

Hey, I've noticed that after updating to 2.0.0 my computed-async with delay stopped working. I've run through the new code and I can see that the delay variable is not used anywhere.

After going back to 1.2.0 everything works fine again.

Polling doesn't work as expected

I realize that this package is meant to be used with MobX. However, I would have expected it to work with manual polling, too.

Consider this Node code:

const { computedAsync } = require('computed-async-mobx');

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

async function main() {
  const observableValue = computedAsync({
    init: 'initial Value',
    fetch: async () => {
      console.log('fetch called');
      await delay(1);
      return 'final value';
    },
  });

  for (let i = 0; i < 5; i++) {
    console.log('Accessing observableValue twice.');
    console.log('observableValue:', { busy: observableValue.busy, value: observableValue.value });
    await delay(1);
  }
}

main();

The output looks like this:

Accessing observableValue twice.
fetch called
fetch called
observableValue: { busy: true, value: 'initial Value' }
Accessing observableValue twice.
fetch called
fetch called
observableValue: { busy: true, value: 'initial Value' }
Accessing observableValue twice.
fetch called
fetch called
observableValue: { busy: true, value: 'initial Value' }
Accessing observableValue twice.
fetch called
fetch called
observableValue: { busy: true, value: 'initial Value' }
Accessing observableValue twice.
fetch called
fetch called
observableValue: { busy: true, value: 'initial Value' }

Each time I access a property of observableValue, its fetch function is re-evaluated. That is unfortunate, but probably cannot be helped; after all, when called by non-observed code, observableValue has no way of knowing whether fetch will return the same value again. What surprises me, though, is that I never get the actual value returned by fetch.

If fetch has been called in the past and has resolved with a value, I'd expect observableValue.value to return that value.

Error when .value is used within computed property

I discovered this nasty bug which I've reproduced here: https://jsfiddle.net/qc26pb4k/2/. With your console open, press "Show". This will trigger the computed being observed, and it will resolve. Then press "Hide". This unmounts a react component, which "unobserves" the computed async value.

It will throw a very cryptic mobx error [mobx] Invariant failed: INTERNAL ERROR only onBecomeUnobserved shouldn't be called twice in a row. It seems like it only happens when .value is used in a computed property. For some reason, unobserving the computed property triggers onBecomeUnobserved twice. Weird.

Also, I apologize for the copy/pasted computed-async-mobx code--it was the only way I could get it running in a fiddle.

(This might actually be a mobx bug, let me know what you think).

Remove special error handling properties (breaking change)

I added special failed/error properties, but this is unnecessary.

Everything can be done inside the fetch function itself, usingtry/catch inside async to transform errors into plain values, or Promise#catch.

The rethrow property should be removed - the behaviour should be to always throw if the value is accessed while in an error state, for consistency with computed in the latest MobX.

asyncComputed not triggering

I am changing the dataset variable but asyncComputed is not triggering.
What could be the cause?

@observable dataset = undefined;

indicators = asyncComputed([], 0, async () => {
    const dataset = this.dataset;
    const indicators = await fetchSomething( {dataset} )
    return indicators
 })

Maybe I forgot .get()?

`busy` property temporarily returns `false`

I noticed that the busy property doesn't behave quite as I would have expected. It seems that when a computedAsync value gets initialized, busy returns false, then quickly switches to true. I would have expected it to return true from the beginning. As it stands, this behavior doesn't play nice with React (via mobx-react).

Consider this code:

import React from 'react';
import ReactDOM from 'react-dom';
import { computedAsync } from 'computed-async-mobx';
import delay from 'delay';
import { observer } from 'mobx-react';

async function timeConsumingOperation() {
  for (let i = 0; i < 5; i++) {
    await delay(500);
    console.log(`Waiting (${i})...`);
  }
}

@observer
class UseCase extends React.Component {
  observableValue = computedAsync({
    init: 'Initial dummy value',
    fetch: async () => {
      await timeConsumingOperation();
      return 'Computed value';
    },
  });

  render() {
    const { value, busy } = this.observableValue;
    console.log('render()', { value, busy });
    return (<ul>
      <li>value: {value}</li>
      <li>busy: {JSON.stringify(busy)}</li>
    </ul>);
  }
}

ReactDOM.render(
  <UseCase />,
  document.body.appendChild(document.createElement('div'))
);

This results in the following console output:

render() Object {value: "Initial dummy value", busy: false}

Warning: forceUpdate(...): Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.

render() Object {value: "Initial dummy value", busy: true}

Waiting (0)...

Waiting (1)...

Waiting (2)...

Waiting (3)...

Waiting (4)...

render() Object {value: "Computed value", busy: false}

It seems that this almost-instantaneous switch from busy: false to busy: true forces React to re-render the component before the first rendering is completely finished.

As a test, I introduced a computed helper property busy that is true if observableValue.busy is true or observableValue.value is the default value. This emulates the behavior I would have expected for observableValue.busy. With this hack in place, React doesn't print the warning.

Intended Usage

Is this a better pattern than the normal way of having actions w/ side effects mutate state after making an async call and then static/normal computed functions derive from that state? Is there something missing from that paradigm that is accomplished here? It's probably obvious but I am missing it.

Will it work with mobx 2.x?

If so, it would be very helpful to have its package version spec to not pull in mobx 3.x in mobx 2.x projects.

Error in mobx strict mode

I am getting following error while using promiseComputed inside mobx project with strict mode enabled.

promisedComputed.js:26 Uncaught Error: [mobx] Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: [email protected]
    at invariant (mobx.module.js:148)
    at fail (mobx.module.js:143)
    at checkIfStateModificationsAreAllowed (mobx.module.js:1186)
    at ObservableValue.prepareNewValue (mobx.module.js:793)
    at ObservableObjectAdministration.write (mobx.module.js:3956)
    at PromisedComputed.set [as refreshCallCount] (mobx.module.js:4179)
    at PromisedComputed.set [as refreshCallCount] (mobx.module.js:369)
    at new PromisedComputed (promisedComputed.js:26)
    at promisedComputed (promisedComputed.js:109)
    at asyncComputed (asyncComputed.js:28)

Enabled flag is

configure({ enforceActions: 'always' });

Mobx Versions:
"mobx": "^5.8.0",
"mobx-react": "^5.4.3",
"mobx-react-lite": "^1.4.1"

Error: promisedComputed must be used inside reactions

Running into a similar error as others (#19) but can't figure out why.
My store has a substore 'data' which is making use of asyncComputed:

//inside data store
@computed get concepts() {
   return this.fetchedConcepts.get()
 }
 fetchedConcepts = asyncComputed([], 0, async () => {
     return await getConcepts({ dataset }) // a promise
 })

The store is then used by a component:

const store = useExplorerStore()

<MyComponent data={store} />

The store is working apart from the store.data properties which are using asyncComputed.
Any hints appreciated ๐Ÿ™

how to manually trigger a refresh of the value

Hi,

this an excerpt of my current code:

export class ConnectorsStore {
  constructor(private _orgStore = orgStore) {}

  promisedConnectors = promisedComputed(null, () => {
    return fetch(`${this._orgStore.selectedOrgId}/connectors/`).catch(e => e);
  });
}

and I'm using the promisedConnectors property inside a React component. The function correctly refetches the "connectors" when the selectedOrgId changes.

However I wonder how I can manually reattempt a fetch / invalidate the cache etc.?
This is in particular required in cases where the fetch request failed / errored and the user wants to retry it (via a "retry" button).
Mobx-utils for example has the refresh method on lazyObservable: https://github.com/mobxjs/mobx-utils#lazyobservable which serves a similar purpose.

I was able to get a similar behaviour with promisedComputed with the following code, but I feel it's kind of a hack:

export class ConnectorsStore {
  constructor(private _orgStore = orgStore) {}

  @observable refreshRequests = 0;

  @action.bound
  refresh() {
    this.refreshRequests++;
  }

  promisedConnectors = promisedComputed(null, () => {
    this.refreshRequests;
     return fetch(`${this._orgStore.selectedOrgId}/connectors/`).catch(e => e);
  });
}

In my react component I would have a a button <Button onClick={connectorsStore.refresh}>Retry</Button>.

Is there a cleaner "refresh" solution with promisedComputed possible?

Thanks and Best Regards Christian

Installation failed because of .git repository

Hi there

Thanks for this library! Could not yet test it but looks promising.

I had an issue during the installation using NPM saying Appears to be a git repo or submodule.. After a quick search I found the problem was that there is a .git directory inside the module root. After removing it everything worked fine (cd node_modules/computed-async-mobx && rm -rf .git).

Just to let you and others know - will now give the library a try ๐Ÿ™‚

Cheers

Using .get() inside a React @observer render function throws an error

Hi @danielearwicker ,

when I use .get() inside the render() function of an @observer I get an error ร— Error: promisedComputed must be used inside reactions. I thought the render function of a React component is also considered an reaction or am I wrong?

Here's a small example

@observer
class ConnectorTable extends React.Component<Props> {
  render() {
    const { connectorsStore } = this.props;

    const connectors = connectorsStore.connectors.get();

    if (!connectors) {
      return (
        <Loader active size="huge">
          Loading Connectors please wait...
        </Loader>
      );
    }

    return (
      <Page title="Connectors">
        <Item.Group>{connectors.map(renderConnector)}</Item.Group>
      </Page>
    );
  }
}
export class ConnectorsStore {

  constructor(private _orgStore = orgStore) {}

  connectors = promisedComputed(null, async () => {
    return fetchConnectors(this._orgStore.selectedOrgId);
  });
}

When I use .value it works as expected (because .value doesn't do the reaction check), but because .value is private typescript complains about it's usage.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.