mobxjs / mobx-utils Goto Github PK
View Code? Open in Web Editor NEWUtility functions and common patterns for MobX
License: MIT License
Utility functions and common patterns for MobX
License: MIT License
This would make it much easier to spot errors in your own code since they'll be output to the console. Additionally it lets the user of the utils decide how they want to handle errors themselves.
Hi, do you think it's reasonable to add a method to viewModal to get current values?
Now I end up using var { localValues, model, isPropertyDirty, ...data } = viewModal
and use the data
I am happy to add it and make PR.
http://codepen.io/jamiewinder/pen/kkLdGa?editors=1010
I found this quite useful. It's handy when you want an element of your state to be updated in a throttled manner, and want access to both the current and incoming values. For example, if you've got a text filtered list somewhere in your application, you can use the 'latest' value as your value (<input value={a.latest} />
) and the current
value as the actual value used (.map(text => text.includes(a.current))
).
I thought I'd float the idea and see what feedback I get. I can tidy this up and make a pull request if there is interest?
it would be cool if you could access the status of a fromPromise as properties instead of accessing the state as strings.
e.g
const promise = Promise.resolve()
fromPromise(promise).status.pending && <span>Pending</span>
instead of
const promise = Promise.resolve()
fromPromise(promise).state === "pending" && <span>Pending</span>
i think it would be a bit easier to spot spelling errors if there was an error thrown.
you could do something similar with case but its still not quite as nice
const promise = Promise.resolve()
promise && fromPromise(promise).case({pending: () => <span>Pending</span>})
thoughts?
PENDING
, FULFILLED
, REJECTED
Hi
I'm trying to reuse fromPromise value for next fromPromise with initalValue, so the value wont be reset between calls and I'm getting this error "Uncaught TypeError: this._observable.get is not a function". I've looked in to the source but don't know exactly why
class Store {
@observable data = fromPromise(Promise.resolve({}));
@action getData(){
this.data = fromPromise(somePromise(),this.data.value);
}
}
first getData call works fine, but the second one throws an Error
Any suggestions?
Thanks for awesome lib!
We mostly use fromPromise
to render loading or other states in react components. Here is a sample component:
@observer
class MyComponent extends React.Component {
@action componentDidMount() {
this.entity = this.props.store.getEntity(); // getEntity returns a `IPromiseBasedObservable`
}
@observable entity = fromPromise(Promise.resolve());
render() {
return this.entity.case({
fulfilled: value => (<div>{JSON.stringify(value)}</div>),
rejected: error => (<div>Error</div>),
pending: () => (<div>Loading entity...</div>)
});
}
}
I have initialized entity
with a resolved fromPromise
to avoid writing an if
in render
method. What I think is really useful is fromPromise(value)
. So fromPromise
does not necessarily needs a Thenable
object.
All the code links in the readme e.g. lib/from-promise.js:100-103 return 404
in TS, when I do:
const someSignal = fromStream(someStream);
I only get someSignal of type:
{
current: {},
dispose: () => ...
}
which ...doesn't seem like a mobx observable.
Is there an example of using fromStream with mobx-react's observer?
Thank you!
Observables created using fromResource
seem to be impossible to use inside a computed
from mainline MobX. Apart from creating derived values, this is also useful for simply wrapping the resulting observables in a familiar interface to be passed to functions that normally received an IComputedValue<T>
rather than a {current(): T, dispose(): void, isAlive(): boolean}
.
Even a simple computed: computed(() => fromResourceObservable.current())
throws an exception.
The exception thrown is an invariant violation of actions being used inside computed
:
Uncaught Error: [mobx] Invariant failed: Computed values or transformers should not invoke actions or trigger other side effects
This happens because it appears that fromResource
's sink
uses an action in its implementation, here:
mobx-utils/src/from-resource.ts
Line 95 in 9b0a137
You can see an example of the problem here http://www.webpackbin.com/4k5kc14BM. I also made a zip of the project, since WebpackBin doesn't let me make the bin immutable.
In the above project, if you render the React component using obsUser
directly, the component works fine. If you render it using computedUser
, you trigger the above exception.
I used exactly the code from the example by @mweststrate in the mobx-utils README with a mock dbUserRecord
wrapped in a computed
.
I ran into this problem myself trying to use MobX to wrap apollo-client
as in your comment here. The reason being that Apollo's subscribe
immediately calls sink
on the stack of its first call, thus tripping this action guard.
All of these problems can be avoided as far as I can tell, by deferring the sink
to occur on the 'next tick', so that it's outside the guards of the computed
's execution, but this is an unreliable solution that should hopefully not be necessary.
Here's a very simple fiddle showing the issue: https://jsfiddle.net/rubyred/t92nfs1r/2/
It seems that generator functions do not work with decorators.
I've tried using @asynAction something = function*() {}
but it does not seem to work properly.
The easiest workaround so far seems to be:
class Foo {
fetch = asyncAction(function* fetch() {
// ... code ...
})
}
It might be a good idea to add this to README :) I can send a PR if you agree.
From https://github.com/mobxjs/mobx-utils/blob/master/src/from-promise.ts#L24
The value of the wrapper is an observable.box
, which means result of the underlying promise will be made deeply observable
The purpose of fromPromise
should be observing the state of the promise itself, and not the state of its value (except the reference). Therefore I propose adding an option (or make it the default behavior, but that would be a breaking change?) to shallowly observe the promise using observable.shallowBox
Hey, I've noticed that all README examples prefix @decorators
with \
. Is it a TypeScript, did I miss something or is it just a typo and I should send a PR? :)
Is it possible to use createViewModel from an ObservableMap?
I'm seeing Uncaught Error: [mobx-utils] createViewModel expects an observable object
.
In the submit
action there's an isObservableArray
and isObservableMap
check which then calls replace
, or clear
and merge
, on the destination
. But I don't think they will be used because of the invariant
check in the constructor only checks for isObservableObject
.
This code will cause an error
myArray = lazyObservable(sink => fetch("/items").then(items => sink(items)), []);
the error is:
TypeError: value.set is not a function
pointed to this line https://github.com/mobxjs/mobx-utils/blob/master/src/lazy-observable.ts#L54
Lets say I have the following model:
interface Model {
firstName: string;
lastName: string;
}
const model: Model = observable({ firstName: "John", lastName: "Doe" });
And the following react component:
@observer
class MyTextBox extends React.Component<{ ... }, {}> {
render() { ... <Input type="text" onChange={this.onTextChange} /> ... }
onTextChange() { ... }
}
And now I want to have a view that has a MyTextBox
for model.firstName
and another one for model.lastName
.
The default approach would be to to pass to functions to MyTextBox
:
<MyTextBox getText={() => model.firstName} setText={val => model.firstName = val;} />
<MyTextBox getText={() => model.lastName} setText={val => model.lastName = val;} />
However, this is cumbersome and error prone as firstName
and lastName
must be referenced multiple times.
It would be very convenient, if I could just bind firstName
somehow to the text value of the text box as supported by Microsofts WPF:
<MyTextBox text={binding(model.firstName)} />
<MyTextBox text={binding(model.lastName)} />
Clearly, binding
can never be implemented in a way that this would work.
Instead of passing a getter and setter, one could combine both to a "reference":
interface Reference<T> {
get(): T;
set(value: T);
}
With the help of typescripts recently introduced keyof-feature, it becomes possible to create a reference to a property in a typesafe manner:
function ref<T, TKey extends keyof T>(obj: T, key: TKey): Reference<T[TKey]> {
// ...
}
Using this function, the example above could be implemented as:
<MyTextBox text={ref(model, "firstName")} />
<MyTextBox text={ref(model, "lastName")} />
TypeScript checks that the properties firstName
and lastName
exist on model
and that they have the required type of Reference<string>
(assuming that text
has this type).
Sadly, TypeScript tooling does not offer completion of the property name.
To overcome that issue, a little bit of black magic could be used to denote the property name:
<MyTextBox text={ref2(model, m => m.firstName)} />
<MyTextBox text={ref2(model, m => m.lastName)} />
With ref2
being implemented as following (ref2
could even be an overloading of ref
):
function ref2<T, TProperty>(obj: T, memberAccessor: (obj: T) => TProperty): Reference<TProperty> {
const dummy: { [key: string]: any } = {};
for (const member in obj)
dummy[member] = member;
const key = memberAccessor(dummy as T) as string;
return ref(obj, key);
}
As the concept of references turned out to be very useful in one of my projects and thus might be useful for others too, I think it might be great to have the interfaces Reference<T>
as well as a ref
-like function in mobx-utils.
What do you think?
When I use now in computed value and use it outside reaction it returns not relevant time.
see https://jsfiddle.net/pwcv8xsu/
I have a few scenarios like this where I have a @computed
property which returns a fromPromise
result:
@observable filter = '';
@computed get filterItems() {
const url = `/loaditems?filter=${this.filter}`;
return fromPromise(getItemsFromUrl(url));
}
This works really well but if the observables that filterItems
uses changes often, specifically mid-flight, we end up continuing a request that is no longer needed. I wonder if a special fromAjax
utility might be useful here, which'd have the following behaviour:
IPromiseBasedObservable
, however the state would have an additional value of 'cancelled'.fromAjax
become unobserved, the underlying AJAX request would be cancelled, and the state of the result would permanently transition to 'cancelled'.This should mean that code like the snippet above should work well; recomputations which cause the fromAjax
result to become unobserved will automatically abort the request in favour of a new request.
I wanted to float the idea before giving it a stab; please poke holes in anything I've posted above!
as shorthand for observable.promise.then
Just spent some time trying to get it to work with the npm package rx but it didn't work. I only noticed I had to use RxJS 5 (npm package rxjs) when I looked at the relevant tests file.
With RxJS 5 the examples work fine.
The errors were:
(toStream)
Uncaught TypeError: Object is not iterable
at getIterable (rx.all.js:2706)
at FromObservable.subscribeCore (rx.all.js:2634)
at FromObservable.tryCatcher (rx.all.js:63)
at setDisposable (rx.all.js:2082)
(fromStream)
Cannot read property fn of undefined
(forgot to save stack trace)
Most usage of fromResource
follow this pattern
function createThing(x) {
let subscription
return fromResouce(
sink => { subscription = x.subscribe(sink) },
() => subscription()
)
}
If the setup function returns the subscription function and if it would be stored in the resource, it could be simplified to:
function createThing(x) {
return fromResouce(
sink => x.subscribe(sink)
)
}
New to mobx-utils, so I suspect this is more a question for clarification than an issue report. Please direct me accordingly and thanks in advance for any help.
What I want to do is use a store that I can refresh when I know new service data is available like lazyObservable
, but easily attach more computed values and action functions. The endgame is to have a method for writing stores that's easy to teach to my team.
Starting from create-react-app, install the shown dependencies and use this app script.
import React from 'react';
import ReactDOM from 'react-dom';
import {action, computed, extendObservable, observable} from 'mobx';
import {observer} from 'mobx-react';
import {asyncAction} from 'mobx-utils';
const flipACoin = () => Math.random() > 0.5;
function determineGodliness() {
const screwYourRAM = new Array(1000000).fill(Math.random());
return new Promise((resolve) => {
setTimeout(() => {
resolve([flipACoin(), flipACoin()]);
}, 1500);
});
}
function godStore() {
const store = observable({
alpha: false,
omega: false,
waiting: false,
refresh: asyncAction(function *() {
this.waiting = true;
[this.alpha, this.omega] = yield determineGodliness();
this.waiting = false;
}),
get isGod() {
return this.alpha && this.omega;
}
});
store.refresh();
return store;
}
window.store = godStore();
const App = observer(({store}) => <p>{
(store.waiting)
? 'The suspense is killing me!'
: (store.isGod)
? 'I am the Alpha and the Omega'
: 'I just work here.'
}</p>);
ReactDOM.render(<App store={window.store} />, document.getElementById('root'));
Task manager shows memory usage increase every time you run window.store.refresh()
in the console. Interestingly enough, using setInterval(window.store.refresh, 3000)
actually causes oscillation in memory usage instead of a linear climb.
What can I do to be sure screwYourRAM
will get garbage collected? All I care about keeping is what's returned from the generator, not what was allocated in the interim.
I've read the source and I understand perfectly how it works and why nested objects aren't supported by createViewModel
, but what would it take to add support for that?
I'm creating ViewModels for objects like this
type DataEntry = {
[key: string]: {
$metadata; {}
value: any
}
} & {
clear(): void
set(data: {}): void
}
With clear
setting all value
s to undefined
and set
taking an object representing the data entry and setting all the corresponding value
s.
Without support for nested objects I can't use either methods or set a value
directly, with severely limits my use of the ViewModel.
I'm totally open to work on this myself, but I'd like to make sure that it makes sense and that it's doable before jumping in.
Thanks.
Perhaps I'm misunderstanding, but shouldn't this print the 1, 2, then 3 in sequence with a 3-second in between each?
http://codepen.io/jamiewinder/pen/qqdgBN?editors=1010
It actually just waits three seconds then processes all three at once.
The following test case demos the issue:
https://jsfiddle.net/1592xe2o/4/
It seems that fromPromise is modifying the jQuery promise in error cases.
I've been trying to turn an async loading pattern I've been using into a decorator. Looks something like this:
const {Component} = React;
const {observable, computed, extendObservable, useStrict} = mobx;
const {lazyObservable, fromPromise} = mobxUtils;
const {observer} = mobxReact;
useStrict(true);
function loadable(initialValue) {
return (target, name, descriptor) => {
const promise = descriptor.get;
const lazyPromise = lazyObservable(
(sink) => sink(fromPromise(promise(), initialValue)),
Promise.resolve(initialValue)
);
const newProps = {};
newProps[name + 'Loading'] = computed(() => lazyPromise.current().state == 'pending');
newProps[name + 'LoadingFailed'] = computed(() => lazyPromise.current().state == 'rejected');
extendObservable(target, newProps);
descriptor.get = () => lazyPromise.current().value;
return descriptor;
};
}
class Store {
@loadable([])
get something() {
return new Promise((resolve, reject) => {
setTimeout(() => {
Math.random() > .25 ? resolve([1,2,3,4,5,6,7,8,9,10]) : reject([]);
}, 5000);
});
}
@computed get somethingFiltered() {
return this.something.filter((e) => e % 2);
}
}
@observer
class LoadableThing extends Component {
render () {
const {store} = this.props;
console.log(store);
if (store.somethingLoading) return <div>Loading...</div>;
if (store.somethingLoadingFailed) return <div>Loading failed...</div>;
return <div>
Original: {store.something.join(',')}<br />
Filtered: {store.somethingFiltered.join(',')}
</div>;
}
}
class App extends Component {
render = () => (
<div>
<LoadableThing store={ new Store() }/>
</div>
);
}
ReactDOM.render(<App/>, document.getElementById('container'));
codepen: http://codepen.io/anon/pen/XMBooq
This seems to work as expected in the codepen, and works for the most part in my node application except for one thing: the computed property that references the loadable property (somethingFiltered
in the example) is always undefined. I've reduced my node application as much as possible to see where things are going wrong, but the behavior is always the same. This leads me to believe the issue has something to do with a difference between Node and the browser, likely involving decorators, since when I remove the decorator and explicitly implement all the different pieces (and initialize the lazyObservable in the constructor), things work fine, even in my Node application.
Can anybody offer a reason why this would not fully work within Node or perhaps a better pattern that I should be using to achieve the desired behavior??
As for now, IPromiseBasedObservable<T>
has this definition:
export interface IPromiseBasedObservable<T> {
value: T;
state: PromiseState;
reason: any;
promise: PromiseLike<T>;
case<U>(handlers: {pending?: () => U, fulfilled?: (t: T) => U, rejected?: (e: any) => U}): U;
}
I suggest to change that to get stronger types:
interface IPromiseBasedObservablePending {
state: "pending"
}
interface IPromiseBasedObservableFulfilled<T> {
state: "fulfilled"
value: T
}
interface IPromiseBasedObservableRejected {
state: "rejected"
value: Error
}
export type IPromiseBasedObservable<T> = (
IPromiseBasedObservablePending |
IPromiseBasedObservableFulfilled<T> |
IPromiseBasedObservableRejected
) & {
promise: PromiseLike<T>;
case<U>(handlers: {pending?: () => U, fulfilled?: (t: T) => U, rejected?: (e: Error) => U}): U;
}
After that typescript will force developer to cover all cases of state and gives strict access to value:
function render(v: IPromiseBasedObservable<string>): string {
return v.value // Property 'value' does not exists on type 'IPromiseBasedObservable<string>'
}
function render(v: IPromiseBasedObservable<string>): string {
if (v.state === "pending") {
return "value is pending"
}
if (v.state === "rejected") {
return `value is rejected with: ${v.value.message}` // v.value is Error here
}
return `value is: ${v.value}`; // v.value is string here
}
Also I suggest to either:
case
function from IPromiseBasedObservableWith strictNullChecks enabled it gives proper check for all cases of v.state
are covered.
It is breaking change and usable only for TS2+, so, it should introduce new major version.
The purpose of initialValue
is to provide a value when the promise final value is not yet available.
From https://github.com/mobxjs/mobx-utils/blob/master/src/from-promise.ts#L50
switch (this.state) {
case "pending": return handlers.pending && handlers.pending();
case "rejected": return handlers.rejected && handlers.rejected(this.value);
case "fulfilled": return handlers.fulfilled && handlers.fulfilled(this.value);
}
The way to distinguish between a fulfilled this.value
and a rejected this.value
is the promise state. Same applies to a pending this.value
since the boxed observable has its value set to initialValue
in the beginning. Therefore I propose that we also pass this.value
to the pending handler. Since I haven't studied more scenario, please tell me if there is any problem that might arise that I'm unaware of.
Passing initialValue
to the pending handler in this case eliminates the need of using closure to access the promise value from the handler (good for composability?)
I can attempt a PR if need be
I'm using VS code and typescript targeting ES2015, so it's using lib.es6.d.ts
for the typings of Promise
. (Incidentally I'm using the isomorphic-fetch lib, but that shouldn't be relevant as I'm working on compiling problems, not run-time). I just found this utils library and thought "fromPromise
is exactly what I need!". My problem is a compilation error on something simple like:
const response = fromPromise(fetch('http://example.com'));
My TypeScript compiler is complaining that Promise
is not assignable to IThenable
- "Types of property then
are incompatible."
I'm hoping this is something I'm missing or easy, but I haven't been able to figure it out yet. Comparing the type definitions may have a clue: Promise has two overloads of then
, though I'm not sure why. I'm including the relevant snippets of the definitions for reference below.
Any help is much appriciated.
From lib.es6.d.ts
:
interface Promise<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): Promise<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): Promise<TResult>;
/**
* Attaches a callback for only the rejection of the Promise.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of the callback.
*/
catch(onrejected?: (reason: any) => T | PromiseLike<T>): Promise<T>;
catch(onrejected?: (reason: any) => void): Promise<T>;
[Symbol.toStringTag]: "Promise";
}
interface PromiseLike<T> {
/**
* Attaches callbacks for the resolution and/or rejection of the Promise.
* @param onfulfilled The callback to execute when the Promise is resolved.
* @param onrejected The callback to execute when the Promise is rejected.
* @returns A Promise for the completion of which ever callback is executed.
*/
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => TResult | PromiseLike<TResult>): PromiseLike<TResult>;
then<TResult>(onfulfilled?: (value: T) => TResult | PromiseLike<TResult>, onrejected?: (reason: any) => void): PromiseLike<TResult>;
}
From mobx-utils:
export interface IThenable<T> {
then<R>(onFulfilled?: (value: T) => IThenable<R> | R, onRejected?: (error: any) => IThenable<R> | R): IThenable<R>;
catch<R>(onRejected?: (error: any) => IThenable<R> | R): IThenable<R>;
done<R>(onFulfilled?: (value: T) => IThenable<R> | R, onRejected?: (error: any) => IThenable<R> | R): IThenable<R>;
nodeify<R>(callback: Function): IThenable<R>;
}
I am using lazyObservable
along with autorun
to keep track of fields from a Dexie database. (Not 100% sure if this is a sane approach as I'm new to MobX, so feel free to tell me if I'm doing it wrong).
Here's how that looks:
export class ObservableDexieTable {
@observable ready = false;
tableLazyFields = {
count: () => this.table.count()
}
constructor(observableDexie, tableName) {
this._observableDexie = observableDexie;
this.tableName = tableName;
for (const [field, callback] of Object.entries(this.tableLazyFields)) {
this[field] = lazyObservable(
sink => callback().then(result => {
console.log("Load field", this.tableName, field, this[field]);
return sink(result);
})
);
}
autorun(
this.table;
untracked(() => {
for (const field of Object.keys(this.tableLazyFields)) {
this[field].refresh();
}
});
);
}
get db() {
return this._observableDexie.db;
}
get table() {
return this._observableDexie.table(this.tableName);
}
}
So, we have a bunch of lazy-loading fields, and we trigger refresh() on them whenever the database changes, to keep their values up to date. (observableDexieTable is another class that uses "dexie-observable" and an atom that fires whenever the database changes).
So lazyObservable works great and our fields aren't loaded until we access them, except when I include that autorun code. As soon as I add that, the field's fetch callback runs immediately, no matter what! I was instead expecting the observable to only run fetch if it is being observed.
I've implemented a simple Promise
that has two observable properties: value
and state
. It is exactly like fromPromise
but it is actually a Promise
. As an example I have converted axios.get
result and:
const myPromise = toObservablePromise(axios.get('/api/get'));
// It is possible to write chained then functions
const myNewPromise = myPromise
.then(response => response.data)
.then(data => console.log(data))
// Also you can write a catch handler
.catch(err => console.error(err));
// And in every step it is possible to observe the state
when(
() => myNewPromise.state === 'fulfilled',
() => {
console.log(myNewPromise.value); // data
});
when(
() => myPromise.state === 'fulfilled',
() => {
console.log(myPromise.value); // response
});
// It is possible to write async tests easily
it('should run this async test without done function', () => {
return myNewPromise
.then(data => {
expect(data).toEqual(...);
});
});
// Also it is possible to watch the whole process using autorun
autorun(() => {
console.log('Promise state=', myNewPromise.state, ', value=', myNewPromise.value);
});
// Promise state=pending, value=undefined
// Promise state=fulfilled, value=<data>
// also you can use Promise.all
Promise.all(myPromise, new ObservablePromise(Promise.resolve('value')), aNativePromise);
Having this gives the feeling that you are working with a native promise 😆 .
Here is a simple implementation for ObservablePromise
and toObservablePromise
(Promise implementation pasted from here):
import { observable } from 'mobx';
/**
* Check if a value is a Promise and, if it is,
* return the `then` method of that promise.
*
* @param {Promise|Any} value
* @return {Function|Null}
*/
function getThen(value) {
const t = typeof value;
if (value && (t === 'object' || t === 'function')) {
const then = value.then;
if (typeof then === 'function') {
return then;
}
}
return null;
}
/**
* Take a potentially misbehaving resolver function and make sure
* onFulfilled and onRejected are only called once.
*
* Makes no guarantees about asynchrony.
*
* @param {Function} fn A resolver function that may not be trusted
* @param {Function} onFulfilled
* @param {Function} onRejected
*/
function doResolve(fn, onFulfilled, onRejected) {
let done = false;
try {
fn((value) => {
if (done) return;
done = true;
onFulfilled(value);
}, (reason) => {
if (done) return;
done = true;
onRejected(reason);
});
} catch (ex) {
if (done) return;
done = true;
onRejected(ex);
}
}
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function ObservablePromise(fn) {
// store state which can be PENDING, FULFILLED or REJECTED
const state = observable.box(PENDING);
// store value once FULFILLED or REJECTED
const value = observable.box(null);
// store sucess & failure handlers
let handlers = [];
function handle(handler) {
if (state.get() === PENDING) {
handlers.push(handler);
} else {
if (state.get() === FULFILLED &&
typeof handler.onFulfilled === 'function') {
handler.onFulfilled(value.get());
}
if (state.get() === REJECTED &&
typeof handler.onRejected === 'function') {
handler.onRejected(value.get());
}
}
}
function fulfill(result) {
value.set(result);
state.set(FULFILLED);
handlers.forEach(handle);
handlers = null;
}
function reject(error) {
value.set(error);
state.set(REJECTED);
handlers.forEach(handle);
handlers = null;
}
function resolve(result) {
try {
const then = getThen(result);
if (then) {
doResolve(then.bind(result), resolve, reject);
return;
}
fulfill(result);
} catch (e) {
reject(e);
}
}
this.done = (onFulfilled, onRejected) => {
// ensure we are always asynchronous
setTimeout(() => {
handle({
onFulfilled,
onRejected
});
}, 0);
};
this.then = (onFulfilled, onRejected) =>
new ObservablePromise((resolveFn, rejectFn) => this.done((result) => {
if (typeof onFulfilled === 'function') {
try {
return resolveFn(onFulfilled(result));
} catch (ex) {
return rejectFn(ex);
}
} else {
return resolveFn(result);
}
}, (error) => {
if (typeof onRejected === 'function') {
try {
return resolveFn(onRejected(error));
} catch (ex) {
return rejectFn(ex);
}
} else {
return rejectFn(error);
}
}));
this.catch = (onRejected) => this.then(null, onRejected);
Object.defineProperty(this, 'value', {
get() {
return value.get();
}
});
Object.defineProperty(this, 'state', {
get() {
return state.get();
}
});
doResolve(fn, resolve, reject);
}
export default function toObservablePromise(promise) {
return new ObservablePromise((resolve, reject) => {
promise.then(resolve, reject);
});
}
export { ObservablePromise };
As for now, mobx has computed
decorators, which could be used on argumentless functions. It would be useful to create "memoize" helper, which acts like lodash memoize function, but with usage of mobx power.
Update: Just saw in the changelog that mobx-utils 2 is already using mobx3.
I'm getting the following in combination with mobx 3:
mobx.js:2604 [mobx] Deprecated: asMap is deprecated, use observable.map or observable.shallowMap instead
mobx-utils/src/create-view-model.ts
Line 15 in b9b3587
My lazyObservable array works great the first time the react component is mounted, and later if I do something like
list.current()[ 5 ].name = "jones"
The table showing the list is re-rendered and I see the new name. But if I
list.current().push( newPerson )
My table is not growing by one.
{ list.current().map( person =>
<Row key={person.id} ...
<Cell>{person.name}</Cell>
What am I missing?
Assuming i have i have observables not part of a prototype (model) they are individual attributes as below
class ExampleStore{
@observable test1
@observable test2
@observable test3
}
i need to do dirty check whether they have changed or not
I'm getting the following error:
Uncaught Error: [mobx] Invariant failed: It is not allowed to create or change state outside an
action
when MobX is in strict mode. Wrap the current method inaction
if this state change is intended
when using the MobxUtils fromStream method in Mobx strict mode. Opting out of strict mode resolves the problem, however I'd strongly like to keep using strict mode.
Here is the callstack:
at invariant (mobx.js:2674)
at checkIfStateModificationsAreAllowed (mobx.js:929)
at ObservableValue.prepareNewValue (mobx.js:2447)
at setPropertyValue (mobx.js:2368)
at StreamListener.set [as current] (mobx.js:2336)
at new StreamListener (observable-stream.js:56)
at fromStream (observable-stream.js:105)
at exports.default (store.js:68)
at Object.<anonymous> (index.js:15)
at __webpack_require__ (bootstrap cbf2afa…:555)
My code from store.js that might be relevant:
// all the rows selected by the user
const selected = observable([])
const
source$ = Observable
.from(toStream(() => selected.slice())),
firstSelected$ = source$
.first()
.map(x => x[0]),
nextToggleSelected$ = source$
.pairwise()
.map(([prev, curr]) => {
if (prev.length < curr.length)
return last(curr)
return difference(prev, curr)[0]
}),
lastToggleSelected$ = Observable.merge(
firstSelected$, nextToggleSelected$
)
const lastToggleSelected = fromStream(lastToggleSelected$)
The last line in that sample is the one from which the error is thrown. The parts at new StreamListener (observable-stream.js:56)
and at fromStream (observable-stream.js:105)
from the transpiled mobx-utils correspond to line 72 this.current = initialValue;
and line 81 this.current = value;
in the observable-stream.ts source file.
Is there any way to fix this without disabling useStrict?
EDIT: Forgot to mention - I tried wrapping fromStream into an action, but it didn't seem to work.
I don't know if this has been brought up before, I'd assume so, but is there any plans to release a ComputedAsync in mobx-utils?
Here it's been developed by a 3rd party: https://github.com/danielearwicker/computed-async-mobx
I personally enjoy this plugin and so far it hasn't failed me. Of course I'd prefer using features that are supported by the MobX community and so maybe there's a reason why the use of ComputedAsync is not a good option.
I know I can achieve the same results of a ComputedAsync with either the fromPromise utils or even just using autorun, but I find it makes the code less readable and intuitive as a ComputedAsync does.
This is probably best explained by example:
https://codesandbox.io/s/L88MnpzRX
(see console)
Basically I have four observables - a
and b
are fromPromise
IPromiseBasedObservable-s, and c
and d
are just boxed values. The autorun displays their values.
I change c
and d
within a runInAction
and as expected the autorun runs once.
I change a
and b
within a runInAction
and not as expected the autorun runs twice - once for each.
Expected output:
?, ?, c , d
?, ?, C , D
A, B, C , D
Actual output:
?, ?, c , d
?, ?, C , D
A, ?, C , D
A, B, C , D
I think this is just an inevitable side-effect of using promises, but I thought I'd raise it.
Is it possible to "bypass" functions from model, including computed and "get function()" ? it looks like that for now it only proxy properties...
I'm not sure if the fromPromise object can be mutated directly or I need to use set function? The same question applies to the createViewModal, it seems that we can mutate viewModal directly without having set function. And can we createViewModal from fromPromise? Thanks!
I've started using keepAlive
to get around an issue where my observable objects become momentarily unobserved, and avoid complex computations (including those that result in network requests) re-running. Sometimes this can be between frames, other times I might want something to hang onto the computed value for a longer time.
So does a keepAlive
timeout make sense? I was going to have a go at this, but I don't know how I can hook into the 'I'm now the last observer, I'll drop my observer in X seconds' situation so I thought I'd float the idea first.
function guard(expr) {
return autorun(() => {
if (!expr) throw "Inviariant violated!"
})
}
guard(() => car.isSpeeding === car.speed > 120)
guard(() => cache.size() === data.length)
// etc
I have a few use cases where I'd like a promise for when an observable is set with a certain value. when
is awesome, but it effectively takes a callback for when the predicate returns true instead of resolving a promise.
I've implemented this as whenAsync
like so:
let whenAsync = fn => new Promise(resolve => when(fn, resolve))
And I use it like this:
await whenAsync(() => !state.someBoolean)
It's especially useful for detecting when boolean values change, e.g. getting a promise for when a modal closes, something finishes loading, etc. It's also useful for testing. We have utils here to convert from a promise to an observable, but not the other way around.
Would something like this be useful as a pull request?
Currently have this in one of my projects
spy((change) => {
if (change.type === 'action' && (change.name === 'update' || change.name === 'select')) {
// setTimeout is because action is "spyed" before mutations take place
setTimeout(() => socket.emit('data', { stuff: this.stuff }), 0)
}
})
Would it make sense as a util that's basically a more controlled autorun
(like reaction
)?
onAction(['update', 'select'], () => socket.emit('data', { stuff: this.stuff }))
Would be very useful in the middle of JSX expressions.
Badly indented example:
<div>
{
promiseObservables.map(o =>
o.case({
pending: () => <p>Loading...</p>,
success: value => <MyComponent prop={value} />,
error: err => <Alert message={err.message} />
}))
}
</div>
I try to react when state
change.. but looks like state
is not a observable...
const result = fromPromise(promise);
console.assert(isObservable(result.state), 'state is NOT an isObservable'); // AssertionError: state is NOT an isObservable
Full code..
https://runkit.com/ridermansb/mobx-react-is-observable/0.0.5
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.