Giter VIP home page Giter VIP logo

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

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

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?

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.

`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.

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

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.

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 ๐Ÿ™

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.

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.

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.

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.

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?

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

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?

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!

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).

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()?

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.

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?

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.

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"

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.