GithubHelp home page GithubHelp logo

mobxjs / mobx Goto Github PK

View Code? Open in Web Editor NEW
27.2K 350.0 1.7K 19.23 MB

Simple, scalable state management.

Home Page: http://mobx.js.org

License: MIT License

JavaScript 42.62% TypeScript 54.99% CSS 0.30% HTML 2.09% Shell 0.01%
mobx reactive-programming react typescript javascript

mobx's People

Contributors

a-gambit avatar andykog avatar asterius1 avatar bnaya avatar capaj avatar cloverich avatar danielkcz avatar dependabot[bot] avatar faassen avatar fi3ework avatar fredyc avatar github-actions[bot] avatar halbgut avatar ichenlei avatar jamiewinder avatar kmalakoff avatar kubk avatar mattruby avatar mweststrate avatar phiresky avatar quanganhtran avatar realityforge avatar rluvaton avatar rossipedia avatar tetsuo avatar tonyraoul avatar urugator avatar vkrol avatar vonovak avatar xaviergonz 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  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

mobx's Issues

Will mobservable perform if used at scale?

Just came across this library and I'm intrigued. Most of the work I'm doing at this point is graph related with a potentially large number of nodes, edges, and properties for each. Any idea how well performance holds up when your state is composed of hundreds of thousands of objects?

Create integration with RxJS

It should be possible to convert RxJS observables into Mobservable observables and vice-versa. Can probably be used by users to build debounce (see #48) and more time related patterns.

Permission to quote.

You credited the "I was reluctant . . ." comment in "Five minute introduction to Mobservable + React" to "anonymous programmer". I want you to know that I stand by my words, now more than ever, and you certainly have my permission to quote me. By the way, my name is "David Schalk", not "Alfred E. Neuman".

I am changing over to mobservable in my Websockets React Game of Score tutorial at "FPComplete. The mouse roll-over, group membership, and find-all-solutions functionality now depend on mobservable. When I am done, "setState" might be completely absent from the code.

deprecate `untracked` ?

One dangerous thing we've encountered in the past with Knockout's computed observables is implicit subscriptions. In cases where you evaluate an observable one time on startup and don't care about future updates, you need to "peek" at the observable.

@observable foo: string;
@observable get bar() {
  return peek(this.foo);
}

Is there a way to peek at an observable without taking the dependency in the computed observable? Sorry if I'm missing it!

Find minimal api surface. Suggestions are welcome!

Hi all,

Since version 0.3 the api of mobservable has hardly changed so I think probably all the important stuff is in the lib. However, I think the current api surface is too large, and I would like to find & move toward a minimal and comprehensible api in version 0.6. So all ideas are welcome!

Here is an overview of the current api. For a first shot at it, see the next comment.

method meaning
commonly used methods
value create a observable / reactive value, either wraps array, computed or reference
sideEffect make a function reactive, and make sure it is always triggered if one of its dependencies has changed, event if it isn't observed itself
props(target?, properties) creates an observable object, or extends an object with observable properties. Doesn't work recursively. Basically applies value to all the values in the properties object and adds it to the target
fromJson turns a JSON tree (acyclic graph) into the same tree in observable form. basically recursively applies array and props
observable property decorator (annotation) for ES6 / typescript, uses props internally
ObservingComponent ES6 decorator for ReactJS that ensures that a component automatically re-renders if any data it observers changes
ObserverMixin same as `ObservingComponent, but for those that do not want to use higher order components but mixins instead. Basically obsoleted by the first one
not so commonly used functions
toJson turns a observable tree into a non observable tree (basically a deep copy)
toPlainValue turns an observable object, value or primitive into a non-observable copy (basically a shallow copy)
array creates an observable array
primitive creates an observable scalar.
reference creates an observable scalar. alias for primitive. Use this if you want to create an observable that refers to a function or array, instead of turning those into an observable as value would do
computed turn a function into a reactive one
expr expr(func) is an alias for computed(func)(), which might be useful if you created observable functions inside observable functions
observeProperty create an observer on a certain property. seems to be obsoleted by `sideEffect with which you can achieve the same
watch observe an expression until it needs to be recomputed. Then invokes a callback instead of applying the actual computation, basically useful to build integrations with other frameworks
batch apply a set of updates but doesn't notify any observers until all updates are executed. basically an optimization

@dslmeinte @ebrpol do you guys have any suggestions on this?

Uncaught TypeError: Cannot delete property 'find a clean mug' of #<Object>

I have greatly improved the project running at http://transcendent.ninja. The README.md is out of date, but the code can be found at https://github.com/dschalk/mobservable-monads .
Today, I have been working on a monad implementation of "Todo List". The store is a reactive object instead of an array, so instead of "splice" I used "delete" followed by the index of the embedded object to be deleted. That results error messages such as:

Uncaught TypeError: Cannot delete property 'find a clean mug' of #<Object>

Here is the constructor of the monad at issue:

let M = new MonadObject(
   {
   'find a clean mug': {
       title: 'Find a clean mug',
       completed: true,
       remove: <button onClick = {() =>  {delete(M.x['find a clean mug']) }}
         style={{fontSize: 22 }}
         >
         Remove 
         </button>
    },

   'make coffee': {
       title: 'Make coffee',
       completed: false,
       remove: <button onClick = {() =>  {delete(M.x['Make coffee']) }}
         style={{fontSize: 22 }}
         >
         Remove
         </button>
    }
  }
);

Here is what appears in the console log:

var M = new MonadObject({
      'find a clean mug': {
        title: 'Find a clean mug',
        completed: true,
        remove: _react2['default'].createElement(
          'button',
          { onClick: function () {
              delete M.x['find a clean mug'];
            },
            style: { fontSize: 22 }
          },
          'Remove'
        )
      },

Hovering the mouse pointer over the little "x" displays: "Uncaught TypeError: Cannot delete property 'find a clean mug' of #".

What I am trying to do seems quite reasonable. I am allowed to splice elements out of arrays, so it is doubly surprising to find that I can't delete properties of an object. I wonder:

  1. Is React or Mobservable causing this?
  2. If it is Mobservable, did you intend to include use cases such as mine in the prohibition against deleting properties of objects.
  3. Is there a work-around that can make it possible for me to remove embedded objects from objects.

My problem has wider implications. Shopping carts are real world applications that need "remove item" buttons. So if Mobservable is responsible for this error and there is a way to remove properties from objects, I suggest that the documentation make it easy to find so developers can quickly get on with their work.

be able to create protected or immutable observables

Based on an idea by @mattmccray: https://gist.github.com/mattmccray/c5b6c69c1653b941ccd7

There are two main directions in which this can be implemented

  1. Protected: so that you cannot accidentally change it by doing an assignment
  2. Full immutable: be never able to change so that mobservable plays nicely with frameworks that expect immutables

When 'mutating' immutable items, observers should still be notified, otherwise data would not be observable anymore.

Protected could be combined with the existing .batch (which should be renamed to .transaction in that case?

Update JSFiddle examples in blog post

Looks like the new API in 0.5.0 broke your excellent blog post.

I would suggest either pinning the JSFiddles to a commit, or reworking them to use mobservable.ObservingComponent. I have done so in this update.

Road to 1.0

Hi all,

Mobservable is quite stable for a while and feels (imho) quite complete. So it's time to work to a 1.0 release. Here are some ideas for 1.0

Improvements

  1. disable consistency checks in production mode for improved performance
  2. simplify error handling for fail fast behavior
  3. include typescript 1.6 typings
  4. docs docs docs
  5. improved mobservable-react-devtools (see the readme for the ideas) (post 1.0)
  6. support nested transactions (post 1.0)
  7. add some utilities to create observables based on time, which is nice for animations and such? (post 1.0)
  8. extend observable objects with utility methods to remove, add and iterate reactive properties? (post 1.0)

Breaking changes

  1. rename makeReactive to observable, to nicely mirror the earlier released rename from sideEffect to `observe.
  2. rename @observable (the property annotation) to obervableProperty or observableSubject -> will remain @observable, the signatures could be overloaded
  3. rename @reactiveComponent to @observer
  4. throw away all already deprecated api's.
  5. the observable function (formerly makeReactive) will no longer be the root export of the module export itself. It will be the default export though.
  6. extendReactive -> extendObservable
  7. isReactive -> isObservable
  8. observe* -> autorun*

Wild ideas

Actually I don't like the name of the module. Initially it wasn't planned to open source it, so it got a boring name. So if people are not too much hindered by it, it might be cool to give it a better name. So cool name suggestions are welcome! (or if you don't like a new name feel free to mention it as well)

Some of my personal ideas:

  • fume
  • (...might add more later)

Typo in README

Small nit: in the README one of the examples says firsName instead of firstName.

autorun won't accept functions that take functions as argumentss.

To demonstrate the issue, I defined a function 'f' in a React.js file as follows:

let f = (h,b,c) => {console.log(h(1,2,b) + c)};
mobservable.autorun(f);

The error message showed:

var f = function f(h, b, c) {
  console.log(h(1, 2, b) + c);
};
_mobservable2['default'].autorun(f);

Hovering over the little red 'x' caused this message to pop up:

Uncaught TypeError: h is not a function;

I didn't get to try out the function because 'autorun' wouldn't accept it. If making 'autorun' take functions that have functions as arguments is not a good idea, or if providing that functionality is a project for another day, then you might want to consider having an error message that says so, and doesn't say the argument is not a function.

Browser support ?

Hello and thank you for sharing this promising library with the community.
It'd be nice to list browser support for mobservable.
In case it cannot be used on some common but older browsers, do we know of any shims/polyfills?
Regards.

Confusing results!

Hello,
I did some test with mobservable but I got unexpected results (it does not work for me the way I intended/wanted). Granted, my use case may seem kind of exotic.
I tried to encapsulate mobservable in some 'isolated' code that we may call 'broker engine'. I need to be able to declaratively define state dependencies between objects that are oblivious to each other. This means, we can have object/modules/components that do not have any reference to anything outside of themselves (totally oblivious to each other). The broker comes into play to declaratively define how these objects' states 'influence' each other.
First tests were positive, however my little attempt has failed when I started to separate things into encapsulated "areas of concern". Basically when I feed the 'plain old object' into some 'broker engine' to make its data reactive and wire different objects together things break.
I was able to track where/when things went wrong. Below is a very simple piece of code that describes the behaviour that caught me out of guard:

var x = {a:1, b:2};
var y = x;
x === y; // is true
x = mobservable.makeReactive(x);
x === y; // suddenly becomes false

In my mind I would have expected x === y; to remain true.
Is there a sensible way to achieve the above described behaviour? I mean being able to pass some object X to a separate engine that does apply the magic of mobservable on X's data without loosing that magic because of the inequality "problem" described in the simple code listing above.
I hope I was not too confusing.

Edit: Ooops, Somehow I missed the x at the left side of the assignment in x = mobservable.makeReactive(x); (been copy pasting it). I came back to see the question and it popped to me :)
There's obviously no problem in the simple code listing above :)

My Small test

Hello everyone,

I really like Mobservable and I'm investigating whether I can use it for the scenario described below.
I want to declaratively "sync" between several objects. For instance, having three object X, Y and Z. I want to declare how those 3 "relate" to each other. So instead of triggering callbacks when one of these objects changes to update/sync the other two. I simply have the declaration dictate how the syncing must be performed.

To this end I did some tests like the one below. where two object (both having the same exact structure for this simple test) must be totally in sync (their variables must have the same values). If one changes any of the two variables the other one would automatically have the same value(s).

The idea is to have both objects totally independent from each other. The responsibility of doing the sync relies on a third system (some kind of broker).

Note that I'm using the global namespace (i.e. window) so as to be able to test this in chrome devtools (in the console)

var mobservable = require('mobservable');

window.wareHouse  = mobservable.makeReactive({ stock:0, price:5 });
window.shop           = mobservable.makeReactive({ stock:0, price:5 });

window.setSP = function(x, newStock, newPrice){ x.stock = newStock; x.price= newPrice;}  

function display(name){
  console.log('Total ['+name+'] =>>>>> '+window[name].price*window[name].stock); 
}

mobservable.sideEffect(function() {
  display('wareHouse');
});

mobservable.sideEffect(function() {
  display('shop');
});

mobservable.sideEffect(function() {
  console.log('wareHouse <- shop ')
  window.wareHouse.stock = window.shop.stock; 
});

mobservable.sideEffect(function() {
  console.log('shop <- wareHouse');
  window.shop.stock = window.wareHouse.stock; 
});

mobservable.sideEffect(function() {
  console.log('wareHouse <- shop ')
  window.wareHouse.price = window.shop.price; 
});

mobservable.sideEffect(function() {
  console.log('shop <- wareHouse');
  window.shop.price = window.wareHouse.price; 
});

When the code above is loaded into the browser (using chrome). I hit F12 and perform the 'tests' below. One line at a time and observe the output on the console to understand what's happening.
Remember, one line at a time.

window.setSP(window.wareHouse, 2, 7);
window.setSP(window.wareHouse, 5, 6);
window.setSP(window.wareHouse, 1, 2);

It basically works. I wonder however if it is possible with Mobservable to make it in such a way that we only have the minimum number of operations. Can we use the fact that the updating in setSP happens atomically on both variables at once to improve this ? (we'r having the syncing triggered for each variable within the setSP function. Could we have it 'trigger' just once? )

PS: I tried to group the functions inside sideEffect (syncing on both variables as the same sideEffect) but it did not work

Support React 0.14

In mobservable-react package, the peer dependencies do not allow React 014 atm, verify the working with React 0.14 and update package.json.

Multiple re-renderes of observing component

Not sure why but I've noticed that my components that are recieving observable props are re-rendering at least twice on when observed data updates. Not that it's a performance problem, I just want to understand how your library works and control things. How do I debug this? What you might need?

Edit: I've noticed that this happens only when I'm updating name through 'parent' model, but it still calls name's __updateFromJson method. When updating name directly, e.g. changing 'value' property or 'is_default' property, component re-renders only once

Edit2: I was wrong, double render happens only when clicked on a link in 'ActionsDropdown' component, then 'onItemSelect' callback is executed calling appropriate 'name' method. But hen calling that same method directly, e.g. 'setAsDefault' component re-renders only once.

Edit3: Another component is showing same behavior and there is also domain object's method called as a callback. The question is why this happens and what is the possible workaround?

Here is my simplified model:

@autobind
class Name {

  id = null;
  store = null;
  nameable = null;

  @observable value;
  @observable is_default;

  constructor(store, nameable, json) {
    this.store = store;
    this.nameable = nameable;
    this.id = json.id;

    this.__updateFromJson(json)
  }

  __updateFromJson(json) {
    this.value = json.value
    this.created_at = json.created_at
    this.updated_at = json.updated_at
    this.is_default = json.is_default
  }

}

And this is my simplified component:

@observer
@autobind
class ShowName extends React.Component {

  getDefaultClass() {
    return this.props.name.is_default ? styles.defaultName : ''
  }

  onItemSelect(itemProps, parent) {
    var action;

    this.setState({ loading: true });

    if (itemProps.action === 'setAsDefault') {
      action = this.props.name.setAsDefault;
    } else if (itemProps.action === 'delete') {
      action = this.props.name.destroy;
    };

    action().then(() => {
      if (this.mounted) this.setState({ loading: false })      
    })
  }

  render() {
    var liClassName = `default-${this.props.name.is_default)} list-group-item entity`; 

    return (
      <li className={ liClassName }>
        <Row>
          <Col
            xs={9}
            className={ `default-${ this.props.name.is_default}` }
          >
            <EditablePopoverLink
              editor={ <NameEditor name={ this.props.name } /> }
              title='Edit name'
              linkText={ this.props.name.value }
            />
          </Col>
    <Col xs={3}>
      <div className="actions pull-right">
        <ActionsDropdown
          isLoading={ this.state.loading }
          isDefault={ this.props.name.is_default }
          onItemSelect={ this.onItemSelect }
        />
      </div>
    </Col>
        </Row>
      </li>
    );
  }

}

Unexpected React warning concerning `forceUpdate` in combination with `jspm-hot-reloader`

Warning: forceUpdate(...): Can only update a mounted or mounting component. This usually means you called forceUpdate() on an unmounted component. This is a no-op. Please check the code for the InputWithCheckmark component

I am developing with jspm-hot-reloader and it seems like when my app rerenders, you still hold references to components which were replaced by the new global rerender.

Why aren't you hooked into componentWillUnmount on the component and call disposer() there?

extendObservable not creating observable for all properties

I have this code here, taken almost straight from the docs:

function Todo(task, store) {
  this.store = store;
  mobservable.extendObservable(this, {
    task: task,
    completed: false,
    count: 0
  })
  console.log(mobservable.isObservable(this.count));   // is false
}

I thought all key-value pairs are made into observables as per: the docs?

add support for structural equality based computations

Sometimes you have computations in views that look like

var coords = mobservable(function() {
    return {
        x : // some computation,
        y : // some computation
    }
});

Now every function that depends on coords will re-evaluate, even if the x and y didn't change; because there is always a fresh object returned.

It should be possible to create (computed) observables that detect changes using deep equality instead of reference equality.

How to constrain independent properties?

Let’s say I have a class Point defined like this:

class Point {
    constructor(x, y) {
        extendReactive(this, {
            x: x,
            y: y
        });
    }
}

Point has two independent observable properties x and y.

For a specific instance of Point I would like to constrain y so that it is always twice x. I’m currently doing it this way:

var p = new Point(10, 20);
sideEffect(() => {
    var newY = p.x * 2;
    setTimeout(() => {p.y = newY;}, 0);
});

I’m using setTimeout because I can’t update the state inside a reactive-view. But it’s pretty clunky. Is there a better way to do this? I know I can use plain JS objects instead of custom classes, but in my actual scenario I need to use custom classes because they are more complex and have methods on them.

Thanks in advance!

Visualize view and data dependencies

All dependencies tracked by mobservable can be represented as a DAG. Let's make something that outputs this in nice JSON format. Intial proposal:

  • startDependencyTreeRecording()
  • stopDependencyTreeRecording(): track and print dependency tree's for all observables that where changed between start and stop.
  • getObserversTree(() => someReactiveThing), print the tree of observers that observe thing
  • getDependencyTree(() => someReactievThing), print the tree of dependencies that are used by thing

To make the tree comprehensible, the options of makeReactive will receive a name property, that will be used to name nodes in the trees (property names will be derived from the name of their parent as well). Also make sure React component names picked up, so you get something like:

getObserversTree(() => todoStore.todos)

{
  name: "todos",
  observers: [
     {
       name: "TodoListView#1",
       obserers: []
     }, {
       name: "todos.getCompletedTodoCount",
       observers: [{
         name: "TodoListView#1"
         observers: []
       }]
     }
   ]
}

... which, could be visualized nicely with some other tool

Is there a way to debounce computed functions?

Say I have a computed observable:

@obserable a: string;
@observable b: string;

@observable get computeTitle() {
  return a + b;
}

If I assign both a and b:

a = 1;
b = 1;

Then computeTitle is called 2 times. This is a waste and definitely adds up in bulk usage. I would like it to be called once, or on demand if it's needed. E.g.

a = 1; // nobody fetched computeTitle, but lets mark it as dirty
b = 2; // nobody fetched computeTitle, but again lets mark it

Then... right before we update react observer components, if there are observers of a dirty computed, we evaluate it so that we can know if we need to tell the observers.

In Knockout, I think there are 2 tools to deal with this. Pure computeds can be used for scenarios like this where there are no side effects of the function being called. If nobody accesses their value, I believe they don't even calculate. I could be wrong though.

Rate limiting is another tool to prevent chatty observables from causing redundant re-evaluations of computed.

Both have their purposes.

Is there anything in mobservable for this? I'd love by default that computed functions do the optimal thing and that you have to do extra work to do suboptimal things (like have the function called on every granular re-evaluation.)

I have heard that there are transactions you can do around assigning multiple things to avoid redundant computes, but didn't find docs on it and haven't dug through the code yet.

@reactiveComponent seems to cause, "Cannot call a class as a function".

Running webpack on the file named B2.jsx produced a well-functioning bundle.js file until a few days ago. Now, @reactiveComponent seems to be causing browsers to crash, reporting:
[mobservable.view '[m#1]'] Caught error during computation: TypeError: Cannot call a class as a function at classCallCheck (http://localhost:3000/bundle.js:64:100) at B2 (http://localhost:3000/bundle.js:85:6) at t.createClass.render ...

All of the code, along with the complete error message, is at https://github.com/dschalk/cow. This is B2.jsx:

 'use strict'
 import React from'react';
 import mobservable from 'mobservable';
 import {reactiveComponent} from 'mobservable-react';
 export {B2};

 // @reactiveComponent class B2 extends React.Component {
 class B2 extends React.Component {
   constructor(props) {
     super(props);
   }
   render = () => {
   return (
     <div> <h1>Cow</h1> </div>
   )}
 }
 React.render(<B2 key='B2' />, document.getElementById('divSix'))

As you see, the last thing I did to make it work was to comment out @reactiveComponent.

Preventing unnecessary updates.

My monad project running at http://transcendent.ninja (code repo at https://github.com/dschalk/javascript-monads and https://github.com/dschalk/mobservable-monads) is coming along better than I dared hope. Lately in my demonstration, I have been updating the DOM with forceUpdate instead of Mobservable or setState. Here is the third paragraph of README.md at https://github.com/dschalk/mobservable-monads:

"Still, I am uncomfortable with having to repeatedly call ".bnd(refresh)". I would like to use Mobservable again, but I don't want it to try to perform updataes at every link in a chain when all I care about is the value of the last monad in the chain. That happens when monads in a chain hold values for a final computation. The residual values in the monads along the chain are of no interest in such a case, although the values of those same monads might be essential in other computations. Currently, I get render() to execute only when I want it to, but the re-render renders things that didn't change, and that squanders resources. I wonder how hard it would be to cause Mobservable to remain dormant until something like "mobservable.refresh()" is called. Hmmm? Michel Weststrate, here comes Issue # 60."

I know my idea is contrary to the usually desirable automatic update feature. Is my concern about Mobservable wasting resources on unnecessary updates valid? I'm thinking of something vaguely like React.shouldUpdate. It would be a function in the Mobservable API that could shut off "observe" for all but one or more designated entities. What do you think?

The second example at mobservable-monads illustrates my concern. The code is:

onClick={() => {mM1.ret(2)
 .bnd(mM2.ret)
 .bnd(square)
 .bnd(mM3.ret)
 .bnd(square)
 .bnd(mM4.ret)
 .bnd(square)
 .bnd(refresh)  }}

Clicking the button causes all four monads to display. I could have interspersed ".bnd(refresh) between every line and gotten the same result. Instead, I call refresh() once, at the end, and all the updates appear in the right column. If my monad values were observable, I wouldn't need to call refresh(), but would mobservable perform four updates, even though one would suffice?

How can rollover button functions alter button colors on hover and focus without changing state during the computation of a reactive view?

What would be the recommended way to change button colors on hover and focus? The console log says I am changing state. How can button colors be reactive and not be part of state?

My rollowver buttons are part of my websockets-react and my mobservable-react-buttons apps at https://github.com/dschalk/websockets-react and .https://github.com/dschalk/mobservable-react-buttons respectively. The apps ore running at http://machinegun.ninja and http://schalk.ninja. I trimmed all but the essential code and posted the buttons code, along with the error warning, at https://github.com/dschalk/bull.git

Transactions: cannot read property 'markReady' of undefined

I think the logic here is to catch some updating changes mid-update.

However, It looks like markReady can alter the changedValues array values which is causing the 'cannot read property 'markReady' of undefined' error.

Should the solution be:

           var length_1 = changedValues.length;
            for (var i = 0; i < length_1; i++)
                !changedValues[i] || changedValues[i].markReady(true);
            changedValues.splice(0, length_1);
            if (changedValues.length)
                throw new Error("[mobservable] Illegal State, please file a bug report");

or

            var values = changedValues.splice(0);
            for (var i = 0, len = values.length; i < len; i++)
                values[i].markReady(true);
            if (changedValues.length)
                throw new Error("[mobservable] Illegal State, please file a bug report");

or do you need me to try to figure out why the issue is happening in the first place? (I've got a transaction that does quite a variety of actions so perhaps values could be removed mid update, but there are done being added so it is not the Illegal State).

Add support for multi argument reactive functions

Currently computed / value only supports functions without arguments, that is, getters.

However, supporting functions with arguments might be nice as well; functions that take a combination of observables and turns it into new observables. (Observable factories)

For example

import {computed, array} from 'mobservable'

var reactiveConcat = computed((listA, listB) => array([].concat(listA,listB)))

var reactiveResultList = reactiveConcat(someReactiveList, otherReactiveList)

where computed(func) for func.length > 0 equals:

function computed(func, scope?) {
    return function() {
        args = arguments;
        return computed(() => {
            value(func.apply(scope, args));
        });
    }
}

And same pattern for sideEffect

Edit: return value should be made reactive as well

Considerations for trees and filtering

This looks very interesting!

I'm currently using redux and the connect decorator to build and filter a tree dynamically. Basically, I use chokidar in my electron app to watch a folder and build an in-memory representation of it. Then, I allow for text-based filtering plus some modes (like flatten or hierarchical) to build another tree that gets rendered reasonably optimally:

  1. maintain a flat representation watching a folder using chokidar and a normalizr-like representation
  2. depending on the search string and mode (flat or hierarchical) and tree node expanded flags, build a flat array to represent the tree (the depth is a numeric property rather than an actual tree)
  3. use connect on each tree node component to pull out a minimal representation into the properties so each component only renders if it actually changed

How would I do something similarly optimized with mobservable?

(basically, I haven't been able to find an example of mobservable for trees yet but think it could simplify my application logic by operating directly on a tree of nodes rather than jumping through hoops with normalr + redux, am wondering about how cascading transformations between different representations should be implemented, and am looking for more clarity on how @observer and @connect are similar in ensuring minimal re-renders given the transformation step).

Behavior clarifications

Could you clarify the following snippets from the docs:

  • synchronous. All updates are processed synchronously, that is, the pseudo expressions a = 3; b -> a * 2; a = 4; print(b); will always print 4; b will never yield a stale value (unless batch is used).

Do you mean that this will print 8? If a = 4 and b = -> a * 2, b() === 8.

  • atomic. All computed values will postpone updates until all inputs are settled, to make sure no intermediate values are visible. That is, the expression a = 3; b -> a * 2; c -> a * b; a = 4; print(c) will always print 36 and no intermediate values like 24.

After a is set, the state is: a = 4, b = -> a * 2, b() === 8, c = -> a * b, so c === 32.

Am I off base somewhere?

Cascading changes `Cycle detected`

I have a case where I need to manage the lifecycle of domain objects (by calling destroy on them) but they are synchronized with another observable so I'm trying to cascade changes, but keep running into the Cycle detected error.

An example flow is:

update a setting with the list of projects -> create or destroy project domain models -> provide an array of current domain models

I originally implemented it like:

class Store
  @observable projects = [];

  constructor() {
   autorun(() => {
      let projects = this.settings.projects.map(path => _.find(this.projects, project => project.path === path) || new Project(path));

      _.difference(this.projects, projects).forEach(project => {project.destroy(); this.projects.remove(project)}); // destroy and remove old -> could have side effects
        this.projects.replace(projects); // `Cycle detected`!
      });
    })); 
  }

// somewhere else
let store = new Store();

So I replaced it with:

   autorun(() => {
      let projects = this.settings.projects.map(path => _.find(this.projects, project => project.path === path) || new Project(path));

      untracked(() => {
        _.difference(this.projects, projects).forEach(project => {project.destroy(); this.projects.remove(project)}); // destroy and remove old -> could have side effects
          this.projects.replace(projects); // removed `Cycle detected`
      });
    })); 

I spoke with someone about this and he suggested one solution could be to use a computed version of the projects (although he suggested simplifying by making the store an observable instead of a class and didn't add the outside variable):

let projects = [];

let store = observable({
  projects: () => {
     projects = this.settings.projects.map(path => _.find(_projects, project => project.path === path) || new Project(path));

      _.difference(this.projects, projects).forEach(project => {project.destroy(); this.projects.remove(project)}); // destroy and remove old -> could have side effects
      return projects;
  }
});

He suggested that because destroy itself could have side effects, that this wasn't a good solution since cycles could be created elsewhere.

I've been running into this a lot where something is observing another property and then needing to cascading update some other observables but when it gets triggered, it is in the middle of an update cycle so I get Cycle detected.

One solution is to try to make all dependent values computed, but with potential side effects, it seems like untracked is the best hammer for all nails?

When using KnockoutJS, I use kb.ignore and peek from time to time to stop auto-subscribing, but don't remember getting cycle warnings since it didn't really track them.

Question: how should cascading changes with and without potential side effects be set up in mobservable?

Sometimes I have to leave a rollover button to get a response, sometimes I don't.

While playing with mobservable (see Monads and Buttons), I have occasionally encountered what seemed to me to be an inexplicable quirk which I fixed with what seemed to me to be a random hack. It happened again today, and although I am undoubtedly showing my ignorance, I feel I would be remiss if I did not pass this information on.
At http://transcendent.ninja, there is a button that says, "Click to run this.ma2.x.ret([10,20,30]).bnd(this.ma2)". When I define "ma2" as "this.ma2 = new MonadArray([]).x, clicking the rollover button has no effect until I move the mouse pointer out of the button. That triggers a render and the expected result gets displayed.

I wanted the result to occur when users click the button, not when their pointer leaves it. I solved the problem by defining "ma2" as "this.ma2 = new MonadArray([0,0,0]).x. When the argument is anything but the empty array, clicking the button gets an immediate response.

I made a couple of stabs at trying to understand mobservable.js; but without some annotation, comprehending it right now would be aweffully time-consuming. I'm having too much fun making buttons and monads with what are still, to me, amazing black boxes.

I'm not having any trouble using mobservable. I mention this only on the off chance that I have stumbled upon some edge case that is of interest to you.

-- David

Why are todoStore methods defined outside the todoStore object?

In the README example, It seemed odd to define the todoStore methods addTodo, removeTodo, and loadTodosAsync outside of todoStore rather than inside like completedCount. I tried to put the method definitions inside of todoStore and the app failed every time. I ended up with this ES6 version:

import mobservable from 'mobservable';
import React from 'react';
var todoStore = mobservable.makeReactive({
    todos: [
        {
            title: 'Find a clean mug',
            completed: true
        },
        {
            title: 'Make coffee',
            completed: false
        }
    ],
    completedCount: function() {
        return this.todos.filter((todo) => todo.completed).length;
    },
    pending: 0
});

todoStore.addTodo = (title) => {
    todoStore.todos.push({
        title: title,
        completed: false
    });
};

todoStore.removeTodo = (todo) => {
    todoStore.todos.splice(todoStore.todos.indexOf(todo), 1);
};

todoStore.loadTodosAsync = () => {
    todoStore.pending++;
    setTimeout(function() {
        todoStore.addTodo('Asynchronously created todo');
        todoStore.pending--;
    }, 2000);
};

class ToList extends React.Component{
    render = () => {
        var store = this.props.store;
        console.log(this.props);
        return (<div>
            <ul>
                { store.todos.map((todo, idx) =>
                    (<TodoView store={ store } todo={ todo } key={ idx } />)
                ) }
                { store.pending ? (<li>Loading more items...</li>) : null }
            </ul>
            <hr/>
            Completed { store.completedCount } of { store.todos.length } items.<br/>
            <button onClick={ this.onNewTodo }>New Todo</button>
            <button onClick={ this.loadMore }>Load more...</button>
        </div>);
    }

    onNewTodo = () => {
        this.props.store.addTodo(prompt('Enter a new todo:', 'Try mobservable at home!'));
    }

    loadMore = () => {
        this.props.store.loadTodosAsync();
    }
};

class ToView extends React.Component{
    render = () => {
        var todo = this.props.todo;
        return (<li>
            <input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } />
            {todo.title}{' '}
            <a href='#' onClick={ this.onEdit }>[edit]</a>
            <a href='#' onClick={ this.onRemove }>[remove]</a>
        </li>);
    }

    onToggleCompleted = () => {
        this.props.todo.completed = !this.props.todo.completed;
    }

    onEdit = (e) => {
        e.preventDefault();
        this.props.todo.title = prompt('Todo:', this.props.todo.title);
    }

    onRemove = (e) => {
        e.preventDefault();
        this.props.store.removeTodo(this.props.todo);
    }
};
let TodoView = mobservable.reactiveComponent(ToView); 
let TodoList = mobservable.reactiveComponent(ToList);
React.render(<TodoList store={todoStore} />, document.getElementById('approot'));

It is fully functional, but I still can't define the methods inside of the todoStore object.

I admit that I haven't thoroughly studied the Mobservable code. Before I do, I wonder if you might give me a hint or two regarding what is going on with defining methods outside of objects. I am very impressed with the fresh thinking behind this library, and with the elegant simplicity of Mobservable, especially in comparison with Flux and its progeny. I am eager to use it, but first I want to prepare a little for whatever surprises might lie ahead.

Renaming sideEffect() to observe() and introducing observeOnce()

During my discussion with @mweststrate of changing the sideEffect() api, we came up with two use cases which can benefit from observeOnce():

  1. Notification System
    You can use mobservable.observeOnce(func) to send a message to the server once the recently created instance of a notification is processed by the client. You would be able to set the notification state to processed=true once the information in the notification is processed.
  2. Checkout Cart
    Once the user payment is processed, the cart is freezed and can’t be rewinded like before the payment.
Old api approach
var dispose = sideEffect(() => {
    if (notification.processed) {
         doServerThing()
         dispose()
   }
});
Suggested api changes by @mweststrate:
once(() => notification.processed), () => doServerThing())`

or to conform with a Promise-like syntax:

when(() => notification.processed).then(() => doServerThing())`

Why is 'onInvalidate' only called once (in observeUntilInvalid)

Hello,
I'm trying to see how to use mobservable to 'intelligently' and efficiently listen to state changes and identified observeUntilInvalid as a plosible 'candidate' to use.
observeUntilInvalid( functionToObserve, onInvalidate )
when it "evaluates" that 'functionToObserve' has a different 'outcome' it runs onInvalidate. However, this only happens once!!! subsequent changes to state that would lead to a different outcome of functionToObserve no longer cause onInvalidate to be ran.
Why is that? Any clues on how to achieve the above scenario?
https://github.com/mweststrate/mobservable/blob/master/docs/api.md#observeuntilinvalidfunctiontoobserve-oninvalidate

PS:
I can see starting line 813 //mobservable.reactiveMixin
it's indeed using observeUntilInvalid within componentWillMount.
But how would that work if componentWillMount is only run when the component is mounted (At least that's implied by its name). It (the 'onInvalidate' function that causes update => '_this.forceUpdate()') won't be re-run when state changes. I must be missing something.

Computeds aren't evaluated before renders after a transaction.

Take this computed example:

@obervable a = true;
@obervable b = false;

@observable get c() {
  return  `C thinks that the b value is: ${ b }`;
}

I have a react component @observer which renders a b and c.

I mutate them in a transaction:

transaction(() => {
   this.a = !this.a;
   this.b = !this.b;
}

Expected after the transaction is complete:

c is evaluated (all computed values should be evaluated before a single render is executed)
render is called (rendering the updated c value)

Resulted:

render is called (rendering old version of c)
c is evaluated with no followup render

Explanation:

Mutating "a" causes the "render" observer to enqueue first.
Mutating "b" causes the computed observer to enqueue second.
It is a bug that the computed isn't resolved first, but even with the bug, I would have expected the render to be called twice.

If you flip the order of mutations, the issue is resolved. But this is a pretty nasty issue. The right thing to do here is to resolve all non-ui dependencies first, and then update any unique component observers that need updating second.

introduce @shallowObservable

When you make an array observable:

@observable foo: [];

... and assign it a value:

foo = [ { bar: 1, baz: ClassType } ];

...the baz value becomes a setter/getter implicitly, so saying something like:

new foo[0].baz() 

Ends up being broken and not what you expect. This is because mobservable is recursing through added things and implicitily observable-ifying them.

Instead what would be more intuitive would be to do shallow observation by default, with optional recursive behavior:

@observable foo: any; 

// This only fires change events when the foo is assigned a new instance. 

foo = {}; // mutates
foo.a = true; // doesn't mutate a by default

Then, if we want foo to recurse, we explain that explicitly:

@observable({ recurse: true }) foo: []; // Does the recursive thing, so saying foo[0].bar = 2 mutates foo.
foo = { a: false }; // mutates, but also instruments the object
foo.a = true; // mutates foo

Arrays in particular are a bit ambiguous and I could go either way. I think i'm ok mutating a vanilla array into an observable array by default, but I'm not keen on going any farther without an opt in behavior.

@observable foo: any[];

foo = []; // mutates foo
foo.push({}); // mutates foo
foo[0].bar = true; // doesn't mutate foo

... but you could opt in of course, to recurse.

Though, a more pure solution would be to require wrapping an array with an observable to make it observable.

foo = observable([]);

ES6 Class Definitions

I noticed in the examples that you use ES6 syntax for everything short of defining React classes. I revised the todo example to use ES6 syntax for everything. One advantage is that I was able to leave the react 'props' object empty in class 'ToList' and substitute 'this store' for 'this.props.store' in the two function definitions. The last three lines of the file are:

let TodoView = mobservable.reactiveComponent(ToView); 
let TodoList = mobservable.reactiveComponent(ToList);
React.render(<TodoList />, document.getElementById('approot'));

React 'props' creation was still very useful in creating ToView components on the fly.
Maybe it would be a good idea to include some examples that not only use ES6 syntax for most things, but also use it to create classes by extending React.Component. If you like, I will make a pull request to include the following example along with package.json and webpack-config.js files so a user could get up and running with 'npm install', 'webpack', and whatever web server they like. I could add webpack-dev-server. I have 'warp' globally install so wherever there is an index.html file, the command 'warp' loads in on port 3000. 'ALT backtick right arrow' on the keyboard followed by 'CTL R' shows me a reloaded webpage. 'ALT backtick right arrow' again takes me back to my 'lilyterm' terminal. I don't feel any pressing need to start using automatic web page reloading again.
Here is my proposed ES6 version of the todo file:

import mobservable from 'mobservable';
import React from 'react';
var todoStore = mobservable.makeReactive({
    todos: [
        {
            title: 'Find a clean mug',
            completed: true
        },
        {
            title: 'Make coffee',
            completed: false
        }
    ],
    completedCount: function() {
        return this.todos.filter((todo) => todo.completed).length;
    },
    pending: 0
});

todoStore.addTodo = (title) => {
    todoStore.todos.push({
        title: title,
        completed: false
    });
};

todoStore.removeTodo = (todo) => {
    todoStore.todos.splice(todoStore.todos.indexOf(todo), 1);
};

todoStore.loadTodosAsync = () => {
    todoStore.pending++;
    setTimeout(function() {
        todoStore.addTodo('Asynchronously created todo');
        todoStore.pending--;
    }, 2000);
};

class ToList extends React.Component{
  constructor(props) {
    super(props);
    this.store = todoStore;
  }
    render = () => {
        var store = this.store;
        return (<div>
            <ul>
                { store.todos.map((item, idx) =>
                    (<TodoView store={ store } todo={ item } key={ idx } />)
                ) }
                { store.pending ? (<li>Loading more items...</li>) : null }
            </ul>
            <hr/>
            Completed { store.completedCount } of { store.todos.length } items.<br/>
            <button onClick={ this.onNewTodo }>New Todo</button>
            <button onClick={ this.loadMore }>Load more...</button>
        </div>);
    }

    onNewTodo = () => {
        this.store.addTodo(prompt('Enter a new todo:', 'Try mobservable at home!'));
    }

    loadMore = () => {
        this.store.loadTodosAsync();
    }
};

class ToView extends React.Component{
    render = () => {
        var todo = this.props.todo;
        return (<li>
            <input type='checkbox' checked={ todo.completed } onChange={ this.onToggleCompleted } />
            {todo.title}{' '}
            <a href='#' onClick={ this.onEdit }>[edit]</a>
            <a href='#' onClick={ this.onRemove }>[remove]</a>
        </li>);
    }

    onToggleCompleted = () => {
        this.props.todo.completed = !this.props.todo.completed;
    }

    onEdit = (e) => {
        e.preventDefault();
        this.props.todo.title = prompt('Todo:', this.props.todo.title);
    }

    onRemove = (e) => {
        e.preventDefault();
        this.props.store.removeTodo(this.props.todo);
    }
};
let TodoView = mobservable.reactiveComponent(ToView); 
let TodoList = mobservable.reactiveComponent(ToList);
React.render(<TodoList />, document.getElementById('approot'));

Reactive graph transformations

I'm really excited to see what you have planned for the "Reactive graph transformations" (well, I took at look at the code/tests, too!).

I'll explain my use case a little more for food for thought...

As chokidar is scanning for folders and files, it is emitting a stream of changes at a high rate so rather than processing them in an incremental fashion (which could be inefficient to render), I'm currently batching them up and them periodically merging them into the scene graph (the "source" representation) and then triggering a full scene render (the "target" representation) which then gets passed down through React to render "reasonably" statically. The way I am currently triggering the render is 1) by using a "modifiedAt" observable timestamp at the top level since there is a single root node and I'm using transactions both in the scene graph and rendered representation phases 2) whenever the view settings change (like the search filter).

So I think there are three main needs:

  1. ease and efficiency of an incremental representation - because I'm batching change streams at the moment, I have made child relationships simple arrays instead of observable arrays because I could have peers of 500+ folders that would keep triggering changes during the batching process. If this was made incremental, I would need to search for where to insert the child into the scene graph and then incrementally determine if the generated child needs to be added to the rendered representation and where to put it.

  2. an efficient way to regenerate the full rendered representation from scratch when the view settings change significantly

  3. synchronicity and render management - because there is an asynchronous flow of chokidar events which may themselves cause more async file system calls, there still might be a small batching step where changes are being resolve asynchronously and them merged into the tree. If many changes appear at once, there may need to be a certain amount of rate limiting based on a framerate target. Like in computer graphics rendering, you sometimes drop frames instead of trying to keep up with an increasing backlog of work. Right now, I'm just measuring each phase and using it to set the maximum number of nodes to process and throttling updates accordingly.

If any of this is way too complicated for the short term, too edge-case, or there are good shortcuts, let me know. It's easy for my to imagine best case scenarios! I'd be happy to discuss and beta test...

Protected Observables

Would it be useful to have support for "protected" observables? Meaning, reactive values that cannot be set outside of a transaction block.

import {makeReactive, transaction} from 'mobservable'

export let authInfo= makeReactive({
  authenticated: false,
  authenticatedAt: null,
  currentUser: null
}, {
  protected: true
})

export function login(user, pass) {
  return api
    .authenticate(user, pass)
    .then( user => {
      transaction( _ => {
        authInfo.authenticated = true
        authInfo.authenticatedAt = new Date()
        authInfo.currentUser = user
      })
      return authInfo.currentUser
    })
}

export function logout() {
  transaction( _ => {
    authInfo.authenticated = false
    authInfo.authenticatedAt = null
    authInfo.currentUser = null
  })
}

Using the above example, if we were to try directly assigning to a protected value (authInfo.authenticated = true) it should fail.

But would it ignore the input or throw an error? Would mobservable take a performance hit to add this? Is this even a good idea? 😨 πŸ’¬

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.