Git Product home page Git Product logo

nuclear-js's Introduction

NuclearJS

Build Status Coverage Status Join the chat at https://gitter.im/optimizely/nuclear-js

Sauce Test Status

Traditional Flux architecture built with ImmutableJS data structures.

Documentation

https://optimizely.github.io/nuclear-js/

Design Philosophy

  • Simple Over Easy - The purpose of NuclearJS isn't to write the most expressive TodoMVC anyone's ever seen. The goal of NuclearJS is to provide a way to model data that is easy to reason about and decouple at very large scale.

  • Immutable - A means for less defensive programming, more predictability and better performance.

  • Functional - The framework should be implemented functionally wherever appropriate. This reduces incidental complexity and pairs well with Immutability.

  • Smallest Amount of State Possible - Using NuclearJS should encourage the modeling of your application state in the most minimal way possible.

  • Decoupled - A NuclearJS system should be able to function without any sort of UI or frontend. It should be backend/frontend agnostic and be able to run on a NodeJS server.

Installation

NuclearJS can be downloaded from npm.

npm install nuclear-js

Examples

  • Shopping Cart Example - Provides a general overview of basic NuclearJS concepts: actions, stores and getters with ReactJS.
  • Flux Chat Example - A classic Facebook flux chat example written in NuclearJS.
  • Rest API Example - Shows how to deal with fetching data from an API using NuclearJS conventions.
  • Hot reloadable stores - Shows how to setup stores to be hot reloadable using webpack hot module replacement.

How NuclearJS differs from other Flux implementations

  1. All app state is in a singular immutable map, like Om. In development you can see your entire application state at every point in time thanks to awesome debugging tools built into NuclearJS.

  2. State is not spread out through stores, instead stores are a declarative way of describing some top-level domain of your app state. For each key in the app state map a store declares the initial state of that key and how that piece of the app state reacts over time to actions dispatched on the flux system.

  3. Stores are not reference-able nor have any getX methods on them. Instead NuclearJS uses a functional lens concept called getters. In fact, the use of getters obviates the need for any store to know about another store, eliminating the confusing store.waitsFor method found in other flux implementations.

  4. NuclearJS is insanely efficient - change detection granularity is infinitesimal, you can even observe computed state where several pieces of the state map are combined together and run through a transform function. NuclearJS is smart enough to know when the value of any computed changes and only call its observer if and only if its value changed in a way that is orders of magnitude more efficient than traditional dirty checking. It does this by leveraging ImmutableJS data structure and using a state1 !== state2 reference comparison which runs in constant time.

  5. Automatic data observation / rendering -- automatic re-rendering is built in for React in the form of a very lightweight mixin. It is also easily possible to build the same functionality for any UI framework such as VueJS, AngularJS and even Backbone.

  6. NuclearJS is not a side-project, it's used as the default Flux implementation that powers all of Optimizely. It is well tested and will continue to be maintained for the foreseeable future. Our current codebase has over dozens of stores, actions and getters, we even share our prescribed method of large scale code organization and testing strategies.

Performance

Getters are only calculated whenever their dependencies change. So if the dependency is a keypath then it will only recalculate when that path in the app state map has changed (which can be done as a simple state.getIn(keyPath) !== oldState.getIn(keyPath) which is an O(log32(n)) operation. The other case is when a getter is dependent on other getters. Since every getter is a pure function, NuclearJS will only recompute the getter if the values of its dependencies change.

API Documentation

API Documentation

For Smaller Applications

NuclearJS was designed first and foremost for large scale production codebases. For a much more lightweight Flux implementation that shares many of the same ideas and design principles check out Microcosm.

Contributing

Contributions are welcome, especially with the documentation website and examples. See CONTRIBUTING.md.

nuclear-js's People

Contributors

ace3251 avatar alansun-optimizely avatar aldanor avatar appsforartists avatar balloob avatar bhamodi avatar c-spencer avatar colindresj avatar gerges-zz avatar gitter-badger avatar goatslacker avatar hankmccoy avatar hnordt avatar jordangarcia avatar kamronbatman avatar kamui avatar kurumpa avatar lchski avatar loganlinn avatar mark-rushakoff avatar nhunzaker avatar onufryk avatar quarklemotion avatar raulmatei avatar sinewyk avatar smarden1 avatar tcoopman avatar thataustin avatar usirin avatar yolk 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

nuclear-js's Issues

Caching-code in evaluator.js does not handle hash collisions

evaluate, __hasStaleValue, __cacheValue, and __isCached all seems to be built on the assumption that two objects with the same hashCode()-result must be identical.
While making this assumptions obviously provides some performance-benefits, it may on occasion lead to completely wrong results.

For the getters, I'd propose that the result of toImmutable(getter) be stored and checked with Immutable.is(), but depending on usage patterns, doing something similar for state may have an unreasonable memory-overhead.

isGetter doesn't actually validate getters

I'd expect isGetter to assert that a getter is valid (e.g. composed only of strings, optionally a trailing function, or other getters). Instead, it tests that an array ends in a function.

Valid getters that fail this test

["path"]

Invalid getters that pass

[1, true, null, () => ({})]

A more robust test

function isGetter(value) {
  return isArray(value) && value.every(
    (item, i) =>    isString(item)
                 || (i === value.length - 1 && isFunction(item))
                 || isGetter(item)
  );
}

It passes all the current getter tests.

Uncaught (in promise) TypeError: Illegal invocation

Getting this at line 46 of change-observer.js.

In Firefox the error is Unhandled promise rejection" TypeError: 'log' called on an object that does not implement interface Console

I'm using Babel as well, maybe this has something to do with it.

Getter recalculation/caching?

The docs don't mention whether Getters are recalculated every time, or just once between Store updates. It seems this implementation should allow at least per-update caching/memoization. Is that implemented/on the roadmap? Could this be mentioned in the docs?

getInitialState: () => null causes failures

Steps to reproduce:

  1. Return null from a Store's getInitialState.
  2. Dispatch an action that the store doesn't handle.

Expected result:

The store ignores the action.

Actual result:

Store handler must return a value, did you forget a return statement

Correct getDataBindings getters in README

In your examples, most of the KeyPaths are only a single level deep. Since you encourage breaking the data architecture into modules, I presume that this is often the case in heavier apps too.

Allowing the user to specify KeyPaths as Strings (while using Arrays for deeply-nested paths) would make the code a bit less verbose. It also would be consistent with the example for getDataBindings:

getDataBindings() {
  return {
    // can reference a reactor KeyPath
    items: 'items',
    taxPercent: 'taxPercent',

I clearly haven't tried Nuclear yet. If Strings are acceptable KeyPaths, the documentation should be updated to reflect that. If not, should they be? In any case, the KeyPaths used in getDataBindings should follow the same rules as the ones used in getters.

Use clearer variables names in the example

In the example, the taxPercent store is setup thusly:

this.on('setTaxPercent', function(percent, value) {
  return value
})

percent is a confusing name, since presumably both values are percentages. Since this is an example, clarity is especially important. I suggest state and payload, to maintain the convention used throughout the rest of the document. oldPercent and newPercent would also be good choices.

Higher order component or container component instead of mixin

As React is moving away from mixins, it might make sense to start talking about alternatives. Maybe something along the lines of:

function nuclearComponent(Component, dataBindings, reactor) {
  return class NuclearComponent extends React.Component {
    constructor() {
      this.state = getState(reactor, dataBindings)
    }

    componentDidMount() {
      var component = this
      component.__unwatchFns = []
      each(dataBindings, function(getter, key) {
        var unwatchFn = reactor.observe(getter, function(val) {
          var newState = {};
          newState[key] = val;
          component.setState(newState)
        })

        component.__unwatchFns.push(unwatchFn)
      })
    }

    componentWillUnmount() {
      while (this.__unwatchFns.length) {
        this.__unwatchFns.shift()()
      }
    }

    render () {
        return <Component {...this.props} {...this.state} />;
    }
  }
}

Which you could then use like:

// ES7
@nuclearComponent(dataBindings, reactor)
class MyComponent extend React.Component {
    // ...
}
// ES6
class MyComponent extend React.Component {
    // ...
}

MyComponent = nuclearComponent(MyComponent, dataBindings, reactor)

Story for Isomorphic applications?

Hi,

Nuclear looks incredibly exciting. I was wondering what the story around creating Isomorphic applications (same code for server and client) might look like.

Since events shouldn't be necessary, I would primarily hope that:

  1. Using the Mixin won't break the application on the server.
  2. getDataBindings could allow an alternative source on the server, OR getInitialState could work on the server, OR an alternative would be provided.

(I'm asking as someone who hasn't yet built complex isomorphic apps and hasn't yet used Nuclear, I apologize for any gaps in my knowledge!)

Thanks!

Inner workings of nuclear

It seems that the react render mixin does NOT use shouldComponentUpdate().
This is pretty surprising because I really expected this... Instead, the components
call setState() if their state observable sees a difference in state...

So when I get this correctly, stateless components are true as the developer
does not have to deal with component state... everything is managed by nuclear; but
state is obviously there - spread all over the component tree.

Doesn't this hidden state all over the component tree trigger setState() more than really
required or does nuclear intelligently detect the topmost component to call setState() for...
leading to a single subtree re-rendering?

It would be great to have updated examples (Todos) to better experiment with nuclear...

Basically calling setState() deep in the component hierarchy is nice to perform
high performant updates on components - but as often propagated, state should
live as high in the component hierarchy as possible... (and therefore cause
a complete re-render of the whole app; something that immutable data makes
fast due to efficient use of shouldComponentUpdate()...)

What is wrong with my understanding of nuclear???

Immediately evaluate reactor.observe

reactor.observe only triggers on changes. It seems like it should trigger immediately using the current result of the getter. Otherwise, if you don't know the present state and don't want to leak memory, you have to do something like this:

var stopObserving;

var onValue = (value) => {
  var completed = doStuffWith(value);

  if (completed)
    stopObserving();
};

onValue(reactor.evaluate(getter));
var stopObserving = reactor.observe(getter, onValue);

Instead of simply reactor.observe(getter, onValue).

This could either be corrected by default, corrected with a flag or new method (like observeImmediately), or left as is. What do you think?

Initial data loading

Is there any functionality available to setup stores with some initial data, stored in a global variable or passed as a parameter to an App.initialize(spec) method. I've seen on other Flux implementations that have similar behavior, e.g.:

Flux.start({
  StoreName: { /* store data */ },
  OtherStore: { /* store data */ }
});

Raul.

Back (state history) or undo (change tracking)?

Hello,

I was at your lightening talk at this meetup, and I seem to remember this library enabled version tracking (aka change history).

I'd like to add "Undo" functionality to an in-brower app, and from the docs, I see this:

Stores are self managing state, providing a single canonical place to define the behavior a domain of your application over time.

But is that talking about tracking high-level state history (like the browser back button needs to return to the previous "page") or granular-level change history (like an undo button needs).

Thanks!

module name "nuclear-js" vs "nuclearjs"

I just spent ages wondering why my installation wasn't working before realising I had used npm install nuclearjs which is a completely different thing. /facepalm

Is it perhaps worth putting a note in the readme highlighting this is not the package named "nuclearjs" on npm?

observe() callback is called for a change in another Store

I have run into a problem where one of my Nuclear app observe callbacks is being called when a different Store is changed than the one it is observing.

My server-api.js file has the following observe call:

initialize: function() {
  reactor.observe('query.query', sendQuery);
}

The sendQuery function is defined like this:

let sendQuery = (query) => {
  console.log("Issuing server request!");
  Actions.setRequestInProgress(true);
  // etc.
  // Then in an async callback
    Actions.setRequestInProgress(false);
};

My DataStore has the following code to register a listener for setRequestInProgress (the QueryStore does not register a listener for setRequestInProgress):

this.on(Const.setRequestInProgress, (state, isInProgress) => {
  console.log('data-store: set isInProgress to ' + isInProgress);
  return state.set('requestInProgress', isInProgress);
});

I have a button that triggers an action that changes query.query. As expected, this triggers my sendQuery method since it was observing query.query.

The Actions.setRequestInProgress(true) action triggers a change in a the DataStore (in the data. requestInProgress keypath) so I can keep track of whether there is a query in progress.

The problem is that while handling the data change for setRequestInProgress, my sendQuery function gets called again, even though nothing new changed in query.query. It's as if it gets called for any data change if there is already a change in progress from an earlier Action.

I was able to work around this by changing my actions in sendQuery to use setTimeout like so:

setTimeout(Actions.setRequestInProgress.bind(null, true), 0);

It seems to me that this should not be necessary though.

Am I doing something weird/wrong or is this a defect?

Thanks!

Ken

Cross module getters

I see you can make getters get data from multiple stores. Can you also have getters get data from multiple modules? (Haven't used nuclear yet, just investigating it)

Why the utilities?

Seems like a strange thing to put in a frontend framework. Why not use the utilities in Immutable.js, or at least split these out?

<Console> has no method 'groupCollapsed'

Congrats, Nuclear is a very interesting project. Now while eagerly trying to integrate it with an existing project based on this isomorphic react stack:

https://github.com/irvinebroque/isomorphic-hot-loader

I get this error message:

TypeError: Object # has no method 'groupCollapsed'
at Object.exports.dispatchStart (/home/mydir/isomorphic-hot-loader/node_modules/nuclear-js/dist/nuclear.js:793:12)

I guess this is due to different rendering environments (server/client)?

Unfortunately I have no idea how I could fix this, any suggestion?

Incompatible implementations of Immutable?

This works:

import {Immutable} from 'nuclear-js';
const List = Immutable.List;

class CommentList extends React.Component {
  static propTypes = {
    comments: React.PropTypes.instanceOf(List).isRequired
  }
}

But this does not:

import {List} from 'immutable';

class CommentList extends React.Component {
  static propTypes = {
    comments: React.PropTypes.instanceOf(List).isRequired
  }
}

Warning:

Failed propType: Invalid prop `comments` supplied to `CommentList`, expected instance of `List`. Check the render method of `UpdatePage`.

Is that supposed to happen? I'm using Webpack, nuclear-js 1.0.5, and immutable 3.7.3.

Reconcile docs with example repo's

The docs have gotten much better for Nuclear but it is unclear when looking through the example repos (todomvc, and tetris) the conventions for creating a reactor. The docs use Nuclear.Reactor passed an object with a stores property containing properties that map to stores created with Nuclear.Store. Todomvc uses Nuclear.Reactor which gets passed an object with actions and state properties. Also, it seems that the Todomvc version of nuclear is the only version compatible with the nuclear-react-mixin.

jordangarcia/nuclear-react-mixin#1

Tetris uses Nuclear.createReactor() which I'm assuming is antiquated, along with reactor.attachCore, reactor.bindActions and reactor.computed.

From this I'm confused on Nuclear best practices, and if only the current docs should be followed.

No rerender on Reactor.dispatch

Following your basic example, I added a form to the component with three input fields for "name", "price" and "quantity". The handleSubmit function for the form looks like this:

  handleSubmit:function() {
    var submitData = {name: this.state.name, price: this.state.price, quantity: this.state.quantity};
    reactor.dispatch('addItem', submitData);
  }

The dispatch itself works perfectly as expected but the components view with the list of items gets not updated/rerenderd.

The console log:

Dispatch: addItem
client.js:30403 payload
client.js:30404 Object {name: "Cheese", price: "2", quantity: "5"}
client.js:30412 Core changed: items
client.js:30419 Dispatch done, new state: Object {items: Array[3], budget: Infinity, taxPercent: 20}

Getting reactor instance into components?

What is the recommended way to get the reactor instance into each component that needs to use it?

I initially just put it on the window object, which works, but that isn't really a good idea. I can pass it into my react-router as below:

Router.run(routes, Router.HistoryLocation, function (Handler, state) {
  React.render(<Handler reactor={reactor}/>, document.body);
});

which gives me the reactor on this.props.reactor, but that is too late for passing into NuclearReactMixin(reactor).

I could setup a singleton wrapper component and import/require that I suppose. What do you guys do?

Drop hard dependency on lodash

Including lodash as a dependency has two undesirable side effects:

  1. Greatly increases the dist size of nuclear
  2. Forces you to use lodash as your application utility library

My preferred solution would be to simply make lodash an optional dependency. I understand that the authors may want to keep using lodash functions, so as an alternative, only include the lodash functions currently being used by nuclear:

var each = require('lodash.foreach'); // instead of require('lodash').each;

Thoughts @dtothefp ?

Update immutable dependency

You're using Immutable-js 3.4.3, but the latest version is 3.7.3. I specifically miss the isEmpty() function on lists.

Console.warn error on reactor.get()

(nuclear-js version 0.5.0-rc4 from npm)

I'm currently testing my nuclear-js stores via mocha, and I keep seeing this output whenever I call reactor.get() using any given KeyPath:

iterable.length has been deprecated, use iterable.size or iterable.count(). This warning will become a silent error in a future version. Error
    at Record.Object.defineProperty.get (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/nuclear-js/dist/nuclear.js:1627:20)
    at isArray (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/nuclear-js/dist/nuclear.js:6848:64)
    at Function.module.exports.isKeyPath (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/nuclear-js/dist/nuclear.js:735:7)
    at evaluate (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/nuclear-js/dist/nuclear.js:768:16)
    at Reactor.get (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/nuclear-js/dist/nuclear.js:194:13)
    at getConsolidatedAppState (/Users/rod/Documents/GitHub/ripple-connect-client-app/app/scripts/shared/NuclearTestUtils.js:32:36)
    at Context.setUp (/Users/rod/Documents/GitHub/ripple-connect-client-app/app/scripts/shared/NuclearTestUtils.js:60:26)
    at callFn (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/mocha/lib/runnable.js:266:21)
    at Hook.Runnable.run (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/mocha/lib/runnable.js:259:7)
    at next (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/mocha/lib/runner.js:271:10)
    at Object._onImmediate (/Users/rod/Documents/GitHub/ripple-connect-client-app/node_modules/mocha/lib/runner.js:292:5)
    at processImmediate [as _immediateCallback] (timers.js:345:15)

NuclearTestUtils.js:32:36 is the reactor.get().

Looking at logs, this seems to happen when an Immutable Record (a Getter) is passed as an argument into the anonymous function whose return value is assigned to isArray on line 6848. When you try to get the length of the Immutable Record, the error on line 1627 gets thrown.

Create generic parts to allow the use in standard flux projects

Really love the ideas behind nuclear-js!

Are there any ambitions to create separate modules of this concept to allow it to be easily
used in a standard flux concept (by creating more generic parts out of nuclear....)?

When I understand it correctly, the parts of nuclear do not differ too
much from common flux patterns...

Enforce immutability for event payloads?

For a framework that enforces a data model composed of immutable data, would it not make sense to enforce immutability for the event payloads?

A common pattern, when updating a store, is to set or deeply merge the event payload into the Immutable.Map or Immutable.List representing the store state. It's error-prone to always have to manually convert the payload to an immutable object - I can see no use-case for ever dispatching a mutable event payload.

Proposal: In flux.dispatch(...), always perform Immutable.fromJS(...) for a non-null payload.

I'm happy to add this and send a pull request, this is just a request to see if anybody else feels this way?

Help: pattern for writing getters depending on state that might be null

I'm having a bit of trouble developing a pattern to handle getters with a dependency on some app state that may be null. See the getters bellow:

// getters.js
exports.org = ['org'];
exports.menus = ['menus'];
exports.selectedMenu = [exports.org, exports.menus, (org, menus) =>
  menus.find(menu => menu.get('id') === org.get('default_menu'))
];

If org and menus are both defined, this is fine. But it may be possible for them to be undefined, which would cause a .get() of undefined.

I know I can code defensively in computed properties to return a reasonable value if they are undefined but this feels like a bad pattern to adapt everywhere.

I believe part of the problem I'm having is that when getDataBindings returns an object (like below) it 'pulls' on the computed properties attempting to get a value

// menu.jsx - requires the getters.js
module.exports = React.createClass({
  mixins: [flux.ReactMixin],

  getDataBindings: function() {
    return {
      menus: Session.getters.menus,
      selectedMenu: Session.getters.selectedMenu,
    };
  },

  render() { ... }
});

I'm assuming that you have a pattern to avoid loading the menu.jsx component if the data required for it hasn't been populated yet? Do you just return false in the parent component's render() if the data isn't there? Am I making any sense?

Rerendering and async workflow

Hi @jordangarcia

I'm currently learning React and the Flux architecture and came to your library when researching how to use Stores with Immutables. I've stumbled upon a couple of problems / questions though when applying it to my example, so let me explain a bit what I did.

In my example I want to fetch some data from an API when the component is rendered. In the ideal scenario with a Flux architecture it would look like this:

  • Component will render
  • Component asks state from Store
  • Store provides default state to component and triggers an Action to fetch data from an API
  • Component renders with initial state
  • Action creator execute async request using some util library
  • Action creator processes request and if successful will dispatch payload
  • Store handles dispatched payload and updates its (immutable) state
  • Store informs Component that it has a new state
  • Component rerender with new state

Now, when I implement this flow using the Reactor the last step is not executed, which could be the same problem @ArnoBuschmann is having #4

As of 1.0.5 the [observe example] loop infinite if Store's init data is Infinity value

    var budgetStore = Nuclear.Store({
    getInitialState: function() {
        return Infinity;
    },
    initialize: function() {
        this.on('setBudget', function(currentBudget, newBudget) {
            return newBudget
        });
    }
    });
    reactor.registerStores({
    budget: budgetStore
    });

    var isOverBudget = [
    totalGetter,
    ['budget'],
    function(total, budget) {
        return total > budget
    }
    ];
    reactor.observe(isOverBudget, function(isOver) {
    // this will be automatically re-evaluated only when the total or budget changes
    if (isOver) {
        var budget = reactor.evaluate(['budget']);
        console.log('Is over budget of ' + budget);
    }
    });

    //this will cause infinite loop
    reactor.dispatch('setBudget', 4);

Fix broken Karma config for Phantom

Running grunt test on master currently results in an error complaining about missing grunt/karma-configs/phantom.karma.js, which indeed it is having been removed in 247dcac. I'm guessing this was inadvertent and the fix might be as simple as adding it back in? If so, I'm glad to open a PR. I figured I'd check though in case this was part of an intentional move away from using Phantom.

reactor#unobserve

Would it make sense to allow a reactor instance's change observer to remove a handler? I think that would be a bit cleaner than saving reference to the observer's unwatch function and calling that at some point

Create a Gitter room

Many of the tools in the React ecosystem have a real-time chat room for their users (and perhaps developers) to discuss best practices and plan for future versions. Gitter is a nice way to provide this, because it's hosted and well-integrated with GitHub.

@jordangarcia should be able to create a room by following this link.

Example with REST interaction

Hi again,

Just checked out the next-update-readme-example branch and very impressed with all the progress you guys are making, on docs and code.

One thing that seems to be missing is examples around interacting with a REST API for fetching/syncing/saving data. I imagine it'd be fairly straightforward, especially along the lines of a "Web API Utils" idea... but if you guys have patterns that work it might be nice to share them so devs know what to expect & how to architect.

My assumed pattern:

// web-api.js

reactor.observe("user", updatedUser => _saveUserToServer(updatedUser));

socket.on("message", (message) => {
  if (message.type === "user") {
    reactor.dispatch("user.update", message.user)
  }
})

function _saveUserToServer(user) {
  reactor.dispatch("user.request", user);
  xhrStuff(url, user).then(resp => {
    reactor.dispatch("user.sync", resp.user)
  }).error(e => reactor.dispatch("user.error", e))
}

One thing I love about Backbone (and the reason I sometimes use it with Flux/React) is that it takes care of all this stuff for you, including tracking when an item is syncing, synced, invalid, or in an error state. It's also incredibly convenient to just set a "url" attribute and then call .save(). I'm not sure if it makes sense to incorporate all of that into Nuclear, but an example showing it off might be worthwhile.

Functional pattern for retrieving deeply nested data

What is the correct way to load deeply nested data using NuclearJS? The chat example shows a basic parent/child relationship where the children are wholly contained within the parent, but in my case the parent and child are related via references. Given that, I'm not sure what the most pragmatic way to load additional levels of data would be (especially since these deeper levels may still need to be fetched from the server if they aren't in a store).

Here is an example of the data in my store:

{
    tasks: {
        1:  {id: 1, name: 'task1', contacts: [{id: 1}], files: [ {id: 1}, {id: 2}]},
        2:  {id: 2, name: 'task2', files: [ {id: 1}, {id: 4}]},
    },
    files: {
        1:  {id: 1, name: 'file1', contacts: [ {id: 1}, {id: 2}]},
        2:  {id: 2, name: 'file2', contacts: [ {id: 1}]},
    },
   contacts: {
        1:  {id: 1, name: 'contact1'},
    }
}      

Currently I have a current-task-id-store that tracks the currently selected task. I can then use this state to grab the files for the task.

// current task id store
function setCurrentTaskID(state, { taskID}) {
  // return the new value of the store's state
  return taskID
}

// task getters
exports.currentTask = [
  ['currentTaskID'],
  exports.tasksMap,
  (currentTaskID, tasksMap) => tasksMap.get(currentTaskID)
]

exports.currentTaskFiles = [
  exports.currentTask,
  ['filesMap']
  (currentTask, filesMap) => ...get files from store / dispatch fetch from server...
]

I then use getDataBindings to get the set of files that I need to render.

// fileSection component
var FileSection = React.createClass({
  mixins: [reactor.ReactMixin],

  getDataBindings() {
    return {
      files: Task.getters.currentTaskFiles
    }
  }, ...

However, now I need to load the contact references for each of those files so that I can render them. I'm not sure how to write a functional getter that would load/fetch them. I thought about just using a binding to get the contactsMap and looking up the values directly but this doesn't seem right and doesn't work in cases where the contact needs to be fetched from the server.

App organization

There's mention of eventually providing documentation around structuring large nuclear apps using modules, but since that's not provided yet, I was hoping to get some insight on the recommended convention.

I've taken a look at Tetris, and see where things are going, but it would be awesome to know more about when/why to use modules and any other org details.

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.