Git Product home page Git Product logo

moreartyjs's Introduction

Morearty.js

NPM version Build Status ![Gitter](https://badges.gitter.im/Join Chat.svg) devDependency Status

Introduction

Morearty.js is a thin layer on top of React (implemented as a mixin) providing better state management facilities in the manner of Om but written in pure JavaScript.

Underneath, Morearty leverages immutable data structures, provided by Facebook's Immutable library, which hold the state of an application. That state is described by a single Binding object and all state transitions are performed through it. When an application component needs to delegate a part of its state to a sub-component, it can create a sub-binding which points to a nested location within the global state and is fully synchronized with the original binding. This way every component knows only what it should know and the entire state is effectively encapsulated. Morearty detects any changes automatically and triggers re-rendering. Moreover, each component gets a correctly defined shouldComponentUpdate method that compares the component's state using straightforward JavaScript strict equals operator ===. So, only the components whose state was altered are re-rendered.

Morearty puts state updates in a render queue and applies them asynchronously in requestAnimationFrame in one pass, falling back to setTimeout when requestAnimationFrame is not available. This dramatically simplifies reasoning about the application and improves performance.

See documentation for more info.

Download

Browser, AMD, Node.js environments are supported. You can get production or development versions. Or just npm install morearty.

Dependencies

Morearty requires React version 0.13 or higher (download) and Immutable 3.7 and above (download). Both should be available as global variables with names React and Immutable unless you're using NPM. Require.js users can do something like:

require.config({
  paths: {
    react: 'path/to/react',
    immutable: 'path/to/immutable'
  }
});

require(['react', 'immutable'], function (React, Imm) {
  window.React = React;
  window.Immutable = Imm;

  require(['component/Bootstrap'], function (Bootstrap) {
    React.renderComponent(
      Bootstrap(),
      document.getElementById('root')
    );
  });
});

Current status

Morearty 0.7 series changes:

  • Support React 0.14 (starting from 0.7.25).
  • React Native support (thanks to @gilbox).
  • Support React 0.13 and Immutable 3.7.
  • Asynchronous rendering is the default, synchronous mode is no longer supported.
  • Support cancelling transactions.
  • Support this.addBindingListener(...) in components for component lifecycle bounded listeners creation. Just listen for changes, all required cleanup is performed in componentWillUnmount automatically.
  • Support renderOnce configuration parameter useful to ensure rendering is performed only once. Other server rendering corrections.
  • Context.bootstrap helper method simplifying application bootstrapping.
  • Support dynamic bindings (#36).
  • Support passing custom React context (#37).
  • Introduced observed bindings and observed props.
  • Support IE8. Deprecate Binding.delete in favor of remove.
  • Support getDefaultMetaState in components.

Morearty 0.6 series changes:

  • React 0.12 and Immutable 3.0 or higher now required.
  • Introduce bindings meta info that allows to store data you don't want to put in the main state, e.g. validation info, history, and so on.
  • Generate less garbage during render.
  • History module migrated on meta binding API.

See releases page for detailed per-release changes descriptions.

Documentation

See Wiki pages for a thourough explanation of Morearty concepts.

Auto-generated API documentation is available here.

Usage

To start using Morearty.js add the script to the page or load it with your favorite AMD loader, e.g. Require.js, and create Morearty context using createContext method:

var Ctx = Morearty.createContext({
  initialState: {
    nowShowing: 'all',
    items: [{
      title: 'My first task',
      completed: false,
      editing: false
    }]
  }
});

When you create components this way, each acquires a correctly defined shouldComponentUpdate method which uses that component's binding (if any) to determine if its state was changed. By default, state is transferred to sub-components in the binding attribute and can be retrieved using the getDefaultBinding method.

TodoMVC

To continue this introduction, TodoMVC implementation based on Morearty.js will be used (repository, application). You should have some previous React knowledge to follow painlessly, only Morearty-specific parts will be described.

App component

Let's now define main application module App:

var NOW_SHOWING = Object.freeze({ ALL: 'all', ACTIVE: 'active', COMPLETED: 'completed' });

var App = React.createClass({
  displayName: 'App',

  mixins: [Morearty.Mixin],

  componentDidMount: function () {
    var binding = this.getDefaultBinding();
    Router({
      '/': binding.set.bind(binding, 'nowShowing', NOW_SHOWING.ALL),
      '/active': binding.set.bind(binding, 'nowShowing', NOW_SHOWING.ACTIVE),
      '/completed': binding.set.bind(binding, 'nowShowing', NOW_SHOWING.COMPLETED)
    }).init();
  },

  render: function () {
    var binding = this.getDefaultBinding();
    return (
      <section id='todoapp'>
        <Header binding={ binding } />
        <TodoList binding={ binding } />
        <Footer binding={ binding } />
      </section>
    );
  }
});

Notice that App uses getDefaultBinding method to retrieve its state binding and delegate it to its children. See getDefaultBinding API doc for the discussion of the default binding concept.

Header component

var Header = React.createClass({
  displayName: 'Header',
  mixins: [Morearty.Mixin],

  componentDidMount: function () {
    this.refs.newTodo.getDOMNode().focus(); // focus on show
  },

  onAddTodo: function (event) {
    var title = event.target.value;
    if (title) {
      this.getDefaultBinding().update('items', function (todos) { // add new item
        return todos.push(Immutable.Map({
          id: currentId++,
          title: title,
          completed: false,
          editing: false
        }));
      });
      event.target.value = '';
    }
  },

  render: function () {
    return (
      <header id='header'>
        <h1>todos</h1>
        <Morearty.DOM.input id='new-todo' // // requestAnimationFrame-friendly wrapper around input
                            ref='newTodo'
                            placeholder='What needs to be done?'
                            onKeyDown={ Morearty.Callback.onEnter(this.onAddTodo) } />
      </header>
    );
  }
});

In onAddTodo method component state is updated by appending new TODO item to the list. render method output custom input component version suitable for rendering in requestAnimationFrame.

TodoList component

var TodoList = React.createClass({
  displayName: 'TodoList',

  mixins: [Morearty.Mixin],

  onToggleAll: function (event) {
    var completed = event.target.checked;
    this.getDefaultBinding().update('items', function (items) {
      return items.map(function (item) {
        return item.set('completed', completed);
      });
    });
  },

  render: function () {
    var binding = this.getDefaultBinding();
    var nowShowing = binding.get('nowShowing');
    var itemsBinding = binding.sub('items');
    var items = itemsBinding.get();

    var isShown = function (item) {
      switch (nowShowing) {
        case NOW_SHOWING.ALL:
          return true;
        case NOW_SHOWING.ACTIVE:
          return !item.get('completed');
        case NOW_SHOWING.COMPLETED:
          return item.get('completed');
      }
    };

    var renderTodo = function (item, index) {
      var itemBinding = itemsBinding.sub(index);
      return isShown(item) ? <TodoItem binding={ itemBinding} key={ itemBinding.toJS('id') } /> : null;
    };

    var allCompleted = !items.find(function (item) {
      return !item.get('completed');
    });

    return (
      <section id='main'>
      {
        items.count() ?
          <Morearty.DOM.input id='toggle-all'
                              type='checkbox'
                              checked={ allCompleted }
                              onChange={ this.onToggleAll } /> :
          null
      }
        <ul id='todo-list'>{ items.map(renderTodo).toArray() }</ul>
      </section>
    );
  }
});

onToggleAll callback sets completed property on all items. Note how state is transferred to the children: only the relevant sub-state is passed using sub method which creates a sub-binding pointing deeper into global state. So, TODO item can only access and modify its own cell, and the rest of application state is protected from incidental modification. val method allows to retrieve the value stored in the binding or in its sub-path.

TodoItem

var TodoItem = React.createClass({
  displayName: 'TodoItem',

  mixins: [Morearty.Mixin],

  componentDidUpdate: function () {
    var ctx = this.getMoreartyContext();
    if (ctx.isChanged(this.getDefaultBinding().sub('editing'))) {
      var node = this.refs.editField.getDOMNode();
      node.focus();
      node.setSelectionRange(0, node.value.length);
    }
  },

  onToggleCompleted: function (event) {
    this.getDefaultBinding().set('completed', event.target.checked);
  },

  onToggleEditing: function (editing) {
    this.getDefaultBinding().set('editing', editing);
  },

  onEnter: function (event) {
    this.getDefaultBinding().atomically()
      .set('title', event.target.value)
      .set('editing', false)
      .commit();
  },

  render: function () {
    var binding = this.getDefaultBinding();
    var item = binding.get();

    var liClass = React.addons.classSet({
      completed: item.get('completed'),
      editing: item.get('editing')
    });
    var title = item.get('title');

    return (
      <li className={ liClass }>
        <div className='view'>
          <Morearty.DOM.input className='toggle'
                              type='checkbox'
                              checked={ item.get('completed') }
                              onChange={ this.onToggleCompleted } />
          <label onClick={ this.onToggleEditing.bind(null, true) }>{ title }</label>
          <button className='destroy' onClick={ binding.remove.bind(binding, '') }></button>
        </div>
        <Morearty.DOM.input className='edit'
                            ref='editField'
                            value={ title }
                            onChange={ Morearty.Callback.set(binding, 'title') }
                            onKeyDown={ Morearty.Callback.onEnter(this.onEnter) }
                            onBlur={ this.onToggleEditing.bind(null, false) } />
      </li>
    );
  }
});

Here component title is written to the global state using set helper when text in changed. To remove the item no callback needs to be passed from the parent: item component just calls Binding's remove method which removes it from the list of items. In onEnter method transaction is used to prevent re-rendering between state transitions. It effectively notifies global listeners once on commit.

Footer component

var Footer = React.createClass({
  displayName: 'Footer',

  mixins: [Morearty.Mixin],

  onClearCompleted: function () {
    this.getDefaultBinding().update('items', function (items) {
      return items.filter(function (item) {
        return !item.get('completed');
      });
    });
  },

  render: function () {
    var binding = this.getDefaultBinding();
    var nowShowing = binding.get('nowShowing');

    var items = binding.get('items');
    var completedItemsCount = items.reduce(function (acc, item) {
      return item.get('completed') ? acc + 1 : acc;
    }, 0);

    return (
      <footer id='footer'>
        <span id='todo-count'>{ items.count() - completedItemsCount + ' items left' }</span>
        <ul id='filters'>
          <li>
            <a className={ nowShowing === NOW_SHOWING.ALL ? 'selected' : '' } href='#/'>All</a>
          </li>
          <li>
            <a className={ nowShowing === NOW_SHOWING.ACTIVE ? 'selected' : '' } href='#/active'>Active</a>
          </li>
          <li>
            <a className={ nowShowing === NOW_SHOWING.COMPLETED ? 'selected' : '' } href='#/completed'>Completed</a>
          </li>
        </ul>
      {
        completedItemsCount ?
          <button id='clear-completed' onClick={ this.onClearCompleted }>
            { 'Clear completed (' + completedItemsCount + ')' }
          </button> :
          null
      }
      </footer>
    );
  }
});

Nothing special here so let's jump straight to...

Starting the application

var Bootstrap = Ctx.bootstrap(App); // will pass root binding to App

React.render(
  <Bootstrap />,
  document.getElementById('root')
);

Ctx.bootstrap method creates Morearty application bootstrap component which is then passed to React render routine.

Principal differences from raw React

You can compare this Morearty-based TodoMVC implementation to the official React version. Main highlights are:

  • No callbacks are passed to sub-components. This becomes especially useful when you find yourself trying to transfer a callback to a component's grand-children (you may never know how your DOM may be restructured after a redesign). There is nothing inherently wrong in passing callbacks to sub-components, but in many cases this can be avoided.
  • No hacks in code simulating immutable state and other tricks (look at the comments within React version sources).
  • Reasoning about the application is much simpler!
  • Each component gets a shouldComponentUpdate method, no need to define it manually (but you can if you like).
  • Less code.

Flux implementation

z3tsu provided Flux version of Todo-MVC based on Reflux: z3tsu/todomvc-morearty-reflux.

React Native support

Starting from version 0.7.16 running on React Native is supported:

var Morearty = require('morearty/native');

Care to help?

Feel free to provide ideas, suggestions, enhancements, documentation improvements. Any feedback or input is highly appreciated. Morearty development is currently driven mostly by feature requests.

Building Morearty

Morearty uses NPM scripts for building: npm run <command> where command is one of:

  • test (run tests);
  • build (run tests and build dist files);
  • doc (generate documentation).

Credits

  • Alexander Semenov @Tvaroh (author)
  • Marat Bektimirov @mbektimirov (collaborator)
  • Tim Griesser @tgriesser (collaborator)
  • Pavel Birukov @r00ger (collaborator)
  • Gil Birman @gilbox (collaborator)
  • Valentine Valyaeff @valff (collaborator)

moreartyjs's People

Contributors

andru avatar byscripts avatar gdeb avatar gdehmlow avatar gilbox avatar gitter-badger avatar jaygood avatar juggernaut- avatar mbektimirov avatar tgriesser avatar tvaroh avatar waltfy 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

moreartyjs's Issues

Meta info set error

The following snippet was working with version 0.7.4:

var Morearty = require('Morearty');
var ctx = Morearty.createContext({
  foo: 'bar'
});
var binding = ctx.getBinding();
binding.sub('foo').meta().set('foo-meta');

console.log(ctx.getBinding().toJS()); 
// { foo: 'bar' }
console.log(binding.sub('foo').meta().toJS()); 
// foo-meta
console.log(binding.sub('foo').toJS());
// bar

With v0.7.5 it fails when setting meta value:

r:\playground\js\node_modules\Morearty\src\Binding.js:66
  return rootValue.updateIn(effectivePath, UNSET_VALUE, function (value) {
                   ^
TypeError: Cannot call method 'updateIn' of undefined
    at setOrUpdate (r:\playground\js\node_modules\Morearty\src\Binding.js:66:20)
    at updateValue (r:\playground\js\node_modules\Morearty\src\Binding.js:73:25)
    at update (r:\playground\js\node_modules\Morearty\src\Binding.js:218:22)
    at Object.freeze.set (r:\playground\js\node_modules\Morearty\src\Binding.js:438:5)
    at Object.<anonymous> (r:\playground\js\mty.js:6:27)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)

CommonJS / splitting modules

Currently, you're using require.js style defines for the project and to that end everything is pretty tightly coupled. There could are several independent components in the project that might be useful to use standalone (Transaction / Binding / Util), but it's not possible to require these individually on the server as independent modules without loading in a Require.js implementation.

Would you be open to a PR which keeps existing functionality / structure but uses CommonJS?

Inconsistent context documentation

From the context documentation I can read in the 'Big picture' section

Both are immutable data structures. This allows to determine if a component's state it's bound to is changed using straightforward equality check.

And in 'Checking if state node is changed' section:

It performs deep comparison of immutable data structures

This seem contradictory to me, unless the ctx.isChanged is not the method used in shouldComponentUpdate or I missed something.

Server side rendering

I tried unsuccessfully to render my application serverside (nodejs). It fails because morearty does its work asynchronously (I think). A two line change in Morearty.js (disabling the renderroutine and returning the result) works in my case.

So, I suggest to allow synchronous rendering serverside. I have actually no idea if that would break stuff (but if Morearty rendering its markup asynchronously only for UI/performance reasons, a sync serverside rendering should be ok).

Add sub-bindings cache

To minimize memory footprint, creating same sub-binding twice should produce same Binding instance.

suggested patterns for updating matching objects in different parts of the binding/tree

With a binding/tree that has the same object in multiple places, are there any suggested patterns for updating the matching objects?

example:

myTree = {
  comment: {user: {id: 1, name: 'John'}},
  blog: {user: {id: 1, name: 'John'}}
}
binding.set(Immutable.fromJS(myTree))
binding.sub('blog.user').set(Immutable.fromJS({name: 'Johnny'}))

Now blog.user no longer matches comment.user, but what if we wanted those identical comment.user and blog.user immutable objects to be simultaneously updated?

Create new paths in Context

I want to add new paths in my context instead of just changing leaf values, so for instance my initial context is:

{ components: {}}

When I add a component, I want the context to be

{components: {component1: {attr1: value1}}

To do so I tried doing

context.getBinding().set(['components','component1','attr1'], value1);

Which does not rerender my views, so I tried

context.getBinding().set(['components','component1'],{'attr1': value1}); 

this correctly rerender my views; but then the object I passed as value is not converted to an immutable map and later calls to context.getBinding().set(['components','component1','attr1'], value2); fail with an invalid keyPath exception. So I also tried

context.getBinding().set(['components','component1'], Immutable.map({'attr1': value1})); 

But then again my components are not updated.
So what I should do to create this new paths correctly?

What is the proper way to work with REST api?

I am interesting in how to implement async server communication and state management with Morearty. I think there are two possible ways to update state:

  • make ajax calls inside the concrete component
  • manage all ajax calls at the root level (like in Flux)

Do you have any working approach for this issue in your company?

Don't fail on React render errors

Morearty should log and keep working on errors during root component's forceUpdate method call.

Add configuration property to set the behavior (log and continue by default).

IE8 Support?

What is the intended level of IE support? immutable.js seems to support IE8 with es5shim.

  • If I submit an IE8 bug report will anyone pursue it?
  • If I submit an IE8 bug fix will it be seriously considered?

I haven't done any IE8 testing yet, just wondering what's on the roadmap.

Ability to copy a context on a sub state only

The use case:
All my components does not share the same ancestors. Thus I have to bootstrap multiple components. I do so by using Ctx.copy() method. But some component are only related to a sub part of the global state. And because the Ctx is always associated to the global state, this component will re-render everytime there is a modification in the state regardless if this modification is relevant to the component.

A possible solution:
Currently, when doing Ctx.copy(), it creates a new context with exactly the same underlying bindings :

  copy: function () {
    return new Context(this._stateBinding, this._metaBinding, this._options);
  }

I would like to be able to "copy" but only for a sub-binding, giving a path for example:

  copy: function (subpath) {
   //TODO what about options?
    return new Context(this._stateBinding.sub(subPath), this._metaBinding.sub(subPath), this._options);
  }

Tested locally and worked as expected.

ReactCSSTransitionGroup causes: Error: Invariant Violation: findComponentRoot

Using Morearty 0.7.4 (sorry, npm is giving me trouble trying to update to 0.7.5)

Is React CSSTransitionGroup supported? If not, are there alternatives for in/out transitions?

When I wrap my elements in a React CSSTransitionGroup, I get the following error:

 Morearty: render error. Continuing. 
Error: Invariant Violation: findComponentRoot(..., .0.2.0.1.0.$=1$51142015-01-07T00=0234=0229=01238Z:0): Unable to find element. 
This probably means the DOM was unexpectedly mutated (e.g., by the browser), usually due to forgetting a <tbody> when using tables, nesting tags like <form>, <p>, or <a>, or using non-SVG elements in an <svg> parent. 
Try inspecting the child nodes of the element with React ID ``. {framesToPop: 1, stack: (...), message: "Invariant Violation: findComponentRoot(..., .0.2.0… the child nodes of the element with React ID ``."}

Just incase it's a bug, I should add that my document ids contain . characters, which has caused me a headache or two elsewhere when working with keypaths as strings instead of arrays.

Multiple component mounting

I'm planning to try moreartyjs, quick question. Is it possible to use the same context when mounting multiple components?
Something like.

var Ctx = Morearty.createContext(
  { // initial state
    nowShowing: 'all',
    items: [{
      title: 'My first task',
      completed: false,
      editing: false
    }]
  },
  {} // initial meta state
);

var Sidebar = require(...);
var Bootstrap = Ctx.bootstrap(Sidebar); // will pass root binding to App
React.render(
  <Bootstrap />,
  document.getElementById('sidebar')
);

//another component that's using the same context
var Menu = require(...);
var Bootstrap = Ctx.bootstrap(Menu); 
React.render(
  <Bootstrap />,
  document.getElementById('menu')
);

Flux example

Since Flux is the FB recommended way to architect React apps do you feel that an example of how to integrate morearty with Flux would be beneficial?

Req for info: testing with jest

Hi, I'm trying to start a simple test using jest and TestUtils, but I'm clearly doing it wrong as I'm dropping into an infinite loop (test fails with max call stack exceeded):

var Morearty = require('morearty');
var React = require('react/addons');
var TopHeader = require('../js/components.js');
var TestUtils = React.addons.TestUtils;

  var Ctx = Morearty.createContext({
    initialState: {
      headerText: 'Off'
    }
  });

describe('TopHeader', function() {
  it('should appear on load', function() {

    var bootstrap = Ctx.bootstrap(TopHeader) //TopHeader is a react component with mixins: [Morearty.Mixin]

    var header = TestUtils.renderIntoDocument(
      React.createElement(bootstrap)
    );

    // Verify that it's Off by default
    var label = TestUtils.findRenderedDOMComponentWithTag(
      header, 'h1');
    expect(label.getDOMNode().textContent).toEqual('Off');

  });
});

The TopHeader component for completeness:

var React = window.React = require('react/addons');
var Morearty = require('morearty');
var el = React.createElement

var TopHeader = React.createClass({
  displayName: 'TopHeader',
  mixins: [Morearty.Mixin],
  render: function(){
    var binding = this.getDefaultBinding();
    var text = binding.sub("headerText")
    return el("h1",{},text)
  }
})

module.exports = TopHeader 

I'm assuming the approach is wrong - any advice gratefully received. If it looks like the right approach I'll do some more digging in to the problem.

Cheers,
Dean

Agressive root component render seem to break React diff

I added a simple text input with a two-way data binding of the form (this is ES6 but nothing fancy):

render() {
 const configuration = this.props.configuration;
 const legendCorner = configuration.sub(['legend', 'corner']);
 const handleChange = function(e) {
   legendCorner.set(e.target.value);
 };
 return <input type="text" onChange={handleChange} value={legendCorner.get()}></input>;
}

The issue I had was that each time I typed something in that input I would loose the focus, which meant for me that React recreated this DOM node although it shouldn't have.
I fixed this by changing Context.bootstrap and moved

    return React.withContext({ morearty: ctx }, function () {
      return React.createFactory(rootComp)({ binding: ctx.getBinding() });
    });

outside of the render method, directly in the bootstrap method. Now my text area doesn't loose focus anymore. The issue I tried to fix might come from another change I made in Morearty I don't have time to investigate, but is there any reason why you recreate the root component on each render as per my understanding of the current code?

Regards

Not really an issue: Thank you!

Hey Alexander & Co.

This is really a GitHub issue only for the lack of any other possibility to reach out to you guys. Here's this:

I just wanted to thank you for providing this awesome library. The way you approach things is really inspiring and on top of that Morearty leverages a lot of my daily work. Your claim to "simplify reasoning about the application and improve performance" really does hold.
Your effort is much appreciated!

Many regards!
Lukas

PS: Close this issue as soon as everybody involved with this project has read it and therefore felt my utmost respect. L.

Unable to update binding from root

If a binding is created on some object or without it, I couldn't update it from root and replace the entire value with new one:

var b = Ctx.Binding.init();
// throws "Error: Path must point to a key"
b.set(Immutable.fromJS({some: 'value'}));

Allow batch transaction commit

Using TransactionContext.commit assumes batch update of underlying data. Now if I made 5 changes inside a transaction I'll receive 5 listener notifications on commit. Desirable behavior: get one listener notification with old and new data. It may be customizable as commit({batch: true}), but I think it should be default behavior, like the classic Atomic transaction.

Request for Info: how does Morearty determine which bindings a component uses?

Hi. When we render a component, how does Morearty understand what bindings the component consumes?

Ideally we'd like to pass in a fairly high level sub-binding and pick and choose the refined sub-bindings that we actually want. We're not sure if this is ok. We're also looking at passing multiple-subbindings in to a component (via secondary props). Is this ok?

Currently we don't understand where Morearty looks to build the shouldComponentUpdate. We're worried that the bindingprop is magical- that it directly determines dependencies for Morearty to build the shouldComponentUpdate handler from. That'd be bad, because we're passing in one high level sub-binding (so it would false trigger yes a lot). It'd also be bad if we pass in other subbindings as props, in which case Morearty wouldn't see that dependency & track it/ add it to the shouldComponentUpdate.

Context apparently not synchronized when calling twice bootstrap

I want to debug the full state of my application, so I did the following:

var Ctx =Morearty.createContext({
 // whatever
});

var MyApp = React.createClass({
  mixins: [Morearty.Mixin],
  render: function(){
    // whatever
  }
});

var StateDebugger = React.createClass({
  displayName: 'StateDebugger',
  mixins: [Morearty.Mixin],
  render: function(){
    var binding = this.getDefaultBinding();
    var fullState = JSON.stringify(binding.get().toJSON(), undefined, 2);
    return <pre>{fullState}</pre>;
  }
});

var Bootstrap = Ctx.bootstrap(MyApp);
var DebugBootstrap = Ctx.bootstrap(StateDebugger);
React.render(<Bootstrap/>, document.getElementById('root'));
React.render(<DebugBootstrap/>, document.getElementById('debug'));

But when I use myApp and change state my debug component is not updated and still prints me the initial state. Is it expected? What should I do to be able to view a part of the state in another part of my document?

shouldComponentUpdate bug with meta() binding

This discussion started here...

I'm unable to make a test that demonstrates a shouldComponentUpdate bug with a normal binding. However I was able to fashion a unit test with a meta binding that incorrectly calculates shouldComponentUpdate... But isn't a meta binding just a subset of a regular binding?. Or maybe I'm misunderstanding how meta bindings work.

update: I also noticed that changing a meta state will only incorrectly trigger update if I also change some (any) non-meta state.

Custom functions undefined after bootstraping the react class

The issue:

The current bootstrap function wrap the given root component into another React class:

bootstrap: function (rootComp, reactContext) {
    var ctx = this;

    var effectiveReactContext = reactContext || {};
    effectiveReactContext.morearty = ctx;

    var root = React.withContext(effectiveReactContext, function () {
      return React.createFactory(rootComp)({ binding: ctx.getBinding() });
    });

    return React.createClass({
      displayName: 'Bootstrap',

      componentWillMount: function () {
        ctx.init(this);
      },

      render: function () {
        return root;
      }
    });
  }

The consequence is that any function defined in the rootComp object is not available after the bootstrap, although it is the case if you simply create the react class.

More precisely, if A is your initial object and the following method is defined A.mymethod, then mymethod will be available in React.render(React.createElement(React.createClass(A)), target)

A dirty solution:
Looking at the code, the only goal of the wrapper is to add :

      componentWillMount: function () {
        ctx.init(this);
      }

Thus, supposing that :
var rootComp = React.createClass(rootCompInner);

we could do the following:

bootstrapObject: function (rootCompInner, additionalProps,  reactContext) {
    var ctx = this;

    //merge the props, I use lodash here
    const props = _.defaults(additionalProps || {}, {binding: ctx.getBinding()});

    var effectiveReactContext = reactContext || {};
    effectiveReactContext.morearty = ctx;

    rootCompInner.mixins.push(Morearty.Mixin);
    rootCompInner.mixins.push({
        componentWillMount: function () {
          ctx.init(this);
        }
    });

    var root = React.withContext(effectiveReactContext, function () {
      var rootComp = React.createClass(rootCompInner);
      return React.createFactory(rootComp)(props);
    });

    return root;
  }

This would return directly a React element (that's why I gave the ability to add props), so it would be a different output type than the current bootstrap method.

The dirty part is that we modify the mixins of the incoming object ... maybe we could workaround it by checking existing mixins.

Provide clean context free build of all app modules

It is hard to use Morearty in own libraries because all its modules directly depend on Immutable. I can't require some Morearty module without passing a context inside my lib. Two things to note here:

  • Immutable should be required as a normal npm module with require('immutable')
  • provide normal non minified build with splited modules

When are explicit transactions appropriate?

Since version 0.7 Morearty renders asynchronously, i.e. it queues binding updates and renders them in one pass. This provides an implicit transaction and frees from the need to use the above API. Still, it's a good idea to use explicit transactions where appropriate to indicate that a set of changes should be applied atomically.

(Emphasis mine)

It seems silly to me to add atomically()/commit() if they don't actually do anything anymore. Am I missing something? What are the specific use cases?

Q: How would you keep your data operations DRY in a more complex example?

Hi!

In your example all the data operations are made within the components themselves. But lets say that we want to have multiple ways of adding an item to a list for example. In this case we would not like to copy-paste that logic into that other component.

In the Flux architecture the stores themselves would be responsible for doing the right thing depending on the the type of action we would like to perform.

In your example the data structures are just immutable structures (which is awesome btw), so where would you put the operations, and how would you invoke them?

What is your view on this?

PS: Great work. This is a really interesting project.

Observed binding documentation

Alternatively, use component's observeBinding method which will register binging as observed for you

binging --> binding

Dynamic Component Binding?

I'm trying to optimize shouldComponentUpdate by only binding to the data that really matters...

Am I not supposed to use a binding whose path is always changing? For example, my render function looks something like this:

var binding = this.getDefaultBinding(),
        currentChartIndexBinding = binding.sub('currentChartIndex'),
        currentChartIndex = currentChartIndexBinding.get(),
        chartBinding = binding.sub('history').sub(currentChartIndex);

    return (
      <div className="fill">
        <FlowChart binding={ {default:chartBinding, index:currentChartIndexBinding } } />
      </div>
    )

So I am binding to currentChartIndexBinding because the chart index might change. But even if the chart index doesn't change, the data in the current chart might change which is why I bind to chartBinding.

As you would expect, currentChartIndexBinding works fine. Unfortunately, what happens is that chartBinding is always one step behind. Ie., in shouldComponentUpdateOverride, which is defined in the FlowChart component, this.getDefaultBinding().toJS() returns the previous value.

Is this a bug, or expected behavior?

Serializing application state

I don't have a reproducible dataset right now, but thought I'd mention that the transit.js code provided in the wiki did not work for me.

I ended up creating a much simpler (possibly less efficient) version of the code which seems to work much more consistently, although it doesn't include an arrayBuilder. This implementation also works for mixed objects (objects which may or may not include mutable objects/arrays in addition to immutable).

Has anyone else experimented with transit-js?

List item binding deletion - meta value out of sync

When deleting a list item which has an assigned meta value, the meta value wont deleted. Instead it will bound to the same index:

var ctx = Morearty.createContext({
  initialState: {
    items: ["foo", "bar", "baz"]
  }
});

var b = ctx.getBinding().sub("items");

b.sub(1).meta().set("bar-meta");
console.log(b.sub(1).toJS(), b.sub(1).meta().toJS());    // bar bar-meta

// let`s remove bar
b.delete(1);
console.log(b.sub(1).toJS(), b.sub(1).meta().toJS());    // baz bar-meta

Is this by design? In my interpretation meta bindings are depend on their parent binding, so when deleting a binding I expect it's meta binding to be deleted, too.

Initial state for meta binding difficulties

I'm trying to setup meta for binding together with initial state of the app:

var ctx = Morearty.createContext(
    {...},
    {...}
)

Then inside App component in render method:

var binding = this.getDefaultBinding();
var meta = binding.meta();
console.log( meta.get() ); // undefined

What am I doing wrong? The meta binding itsef contains this data, I can see it in Chrome Inspector.

Enhancement: React Developer Tools debuggability of bindings

The React Developer Tools have the excellent & astounding capability of viewing React state. However in Morearty state is diffused into the context.

I would like to see some means to add traceability back into the developer tools. This means that components need some way to expose which sub-bindings they hold and use, such that developer tools can introspect and present the bound data.

npm bug?

Has anyone seen this?

npm install morearty -S
rm -rf node_modules/
npm install

  npm ERR! peerinvalid The package immutable does not satisfy its siblings' peerDependencies requirements!
  npm ERR! peerinvalid Peer [email protected] wants immutable@~3.5.x

Modifying the package.json from "immutable": "^3.5.0", to "immutable": "~3.5.0", fixes it

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.