Skip to content

Commit

Permalink
rewrote React TodoMVC from using immutable data and subscriptions to …
Browse files Browse the repository at this point in the history
…using observables
  • Loading branch information
mweststrate committed Jul 11, 2015
1 parent 7b27e9d commit 2e30cae
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 103 deletions.
4 changes: 3 additions & 1 deletion examples/react-mobservable/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
<section id="todoapp"></section>
<footer id="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://github.com/petehunt/">petehunt</a></p>
<p>Created by <a href="http://github.com/mweststrate/">mweststrate</a></p>
<p>Based on the base React TodoMVC by <a href="http://github.com/petehunt/">petehunt</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>

<script src="node_modules/mobservable/dist/mobservable.js"></script>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/react/dist/react-with-addons.js"></script>
<script src="node_modules/react/dist/JSXTransformer.js"></script>
Expand Down
35 changes: 14 additions & 21 deletions examples/react-mobservable/js/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global React, Router*/
/*global React, Router, mobservable*/
var app = app || {};

(function () {
Expand All @@ -16,7 +16,8 @@ var app = app || {};

var ENTER_KEY = 13;

var TodoApp = React.createClass({
// This decorator makes sure that this component re-renders if any observed model data is changed.
var TodoApp = mobservable.ObservingComponent(React.createClass({
getInitialState: function () {
return {
nowShowing: app.ALL_TODOS,
Expand Down Expand Up @@ -110,11 +111,8 @@ var app = app || {};
);
}, this);

var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);

var completedCount = todos.length - activeTodoCount;
var activeTodoCount = model.activeTodoCount;
var completedCount = model.completedCount;;

if (activeTodoCount || completedCount) {
footer =
Expand Down Expand Up @@ -159,17 +157,12 @@ var app = app || {};
</div>
);
}
});

var model = new app.TodoModel('react-todos');

function render() {
React.render(
<TodoApp model={model}/>,
document.getElementById('todoapp')
);
}

model.subscribe(render);
render();
})();
}));

var model = new app.TodoModel('react-mobservable-todos');
// Render once, all subscriptions are managed automatically
React.render(
<TodoApp model={model}/>,
document.getElementById('todoapp')
);
})();
21 changes: 4 additions & 17 deletions examples/react-mobservable/js/todoItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/*jshint white: false */
/*jshint trailing: false */
/*jshint newcap: false */
/*global React */
/*global React, mobservable */
var app = app || {};

(function () {
Expand All @@ -11,7 +11,7 @@ var app = app || {};
var ESCAPE_KEY = 27;
var ENTER_KEY = 13;

app.TodoItem = React.createClass({
app.TodoItem = mobservable.ObservingComponent(React.createClass({
handleSubmit: function (event) {
var val = this.state.editText.trim();
if (val) {
Expand Down Expand Up @@ -44,20 +44,7 @@ var app = app || {};
return {editText: this.props.todo.title};
},

/**
* This is a completely optional performance enhancement that you can
* implement on any React component. If you were to delete this method
* the app would still work correctly (and still be very performant!), we
* just use it as an example of how little code it takes to get an order
* of magnitude performance improvement.
*/
shouldComponentUpdate: function (nextProps, nextState) {
return (
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText
);
},
// ObservingComponent optimizes shouldComponentUpdate for us, so no need to do that manually

/**
* Safely manipulate the DOM after updating the state when invoking
Expand Down Expand Up @@ -102,5 +89,5 @@ var app = app || {};
</li>
);
}
});
}));
})();
97 changes: 50 additions & 47 deletions examples/react-mobservable/js/todoModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,86 +2,89 @@
/*jshint white:false */
/*jshint trailing:false */
/*jshint newcap:false */
/*global mobservable */

var app = app || {};

(function () {
'use strict';

var Utils = app.Utils;
// Generic "model" object. You can use whatever
// framework you want. For this application it
// may not even be worth separating this logic
// out, but we do this to demonstrate one way to
// separate out parts of your application.
app.TodoModel = function (key) {
this.key = key;
this.todos = Utils.store(key);
this.onChanges = [];
};

app.TodoModel.prototype.subscribe = function (onChange) {
this.onChanges.push(onChange);
// This is the model of our data. We aslo put derived data in here, for easier re-use,
// Note that all data in the model is reactive
// So changes in the todo's array automatically propage to computed properties like activeTodoCount
// And to any relevant components a piece of data of the model that was used inside that component is changed.

This comment has been minimized.

Copy link
@mweststrate

mweststrate Jul 11, 2015

Author Owner

And to any relevant components if a piece of data of the model that ...

app.TodoModel = function(key) {
mobservable.props(this, {
key: key,
todos: [],
activeTodoCount: function() {
return this.todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);
},
completedCount: function() {
return this.todos.length - this.activeTodoCount;
}
});
this.readModelFromLocalStorage();
this.subscribeLocalStorageToModel();
};

app.TodoModel.prototype.inform = function () {
Utils.store(this.key, this.todos);
this.onChanges.forEach(function (cb) { cb(); });
app.TodoModel.prototype.readModelFromLocalStorage = function(model) {
Utils.getDataFromStore(this.key).map(function(data) {
this.todos.push(new app.Todo(data.id, data.title, data.completed));
}, this);
}

app.TodoModel.prototype.subscribeLocalStorageToModel = function(model) {
// store the model whenever the key or todos (or something inside that) changes
// localStorage itself isn't an observing thing, so that is why we rely
// on sideEffect to observe and pass on the data to the storage
mobservable.sideEffect(function() {
Utils.storeData(this.key, this.todos);
}, this);
};

app.TodoModel.prototype.addTodo = function (title) {
this.todos = this.todos.concat({
id: Utils.uuid(),
// Class that represents a Todo item (it is not necessary to use a class, but it is nice)
app.Todo = function(id, title, completed) {
mobservable.props(this, {
id: id,
title: title,
completed: false
completed: completed
});
};


this.inform();
app.TodoModel.prototype.addTodo = function (title) {
this.todos.push(new app.Todo(Utils.uuid(), title, false));
// Note: no inform() calls anymore! All changes in the model
// are automatically propagated to the proper components
};

app.TodoModel.prototype.toggleAll = function (checked) {
// Note: it's usually better to use immutable data structures since they're
// easier to reason about and React works very well with them. That's why
// we use map() and filter() everywhere instead of mutating the array or
// todo items themselves.
this.todos = this.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
this.todos.forEach(function (todo) {
todo.completed = checked;
});

this.inform();
};

app.TodoModel.prototype.toggle = function (todoToToggle) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToToggle ?
todo :
Utils.extend({}, todo, {completed: !todo.completed});
});

this.inform();
todoToToggle.completed = !todoToToggle.completed;
};

app.TodoModel.prototype.destroy = function (todo) {
this.todos = this.todos.filter(function (candidate) {
return candidate !== todo;
});

this.inform();
this.todos.remove(todo);
};

app.TodoModel.prototype.save = function (todoToSave, text) {
this.todos = this.todos.map(function (todo) {
return todo !== todoToSave ? todo : Utils.extend({}, todo, {title: text});
});

this.inform();
todoToSave.title = text;
};

app.TodoModel.prototype.clearCompleted = function () {
this.todos = this.todos.filter(function (todo) {
return !todo.completed;
});

this.inform();
};

})();
6 changes: 4 additions & 2 deletions examples/react-mobservable/js/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ var app = app || {};
return count === 1 ? word : word + 's';
},

store: function (namespace, data) {
storeData: function (namespace, data) {
if (data) {
return localStorage.setItem(namespace, JSON.stringify(data));
localStorage.setItem(namespace, JSON.stringify(data));
}
},

getDataFromStore: function(namespace) {
var store = localStorage.getItem(namespace);
return (store && JSON.parse(store)) || [];
},
Expand Down
1 change: 1 addition & 0 deletions examples/react-mobservable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"private": true,
"dependencies": {
"director": "^1.2.0",
"mobservable": "^0.5.2",
"react": "^0.13.3",
"todomvc-app-css": "^1.0.0",
"todomvc-common": "^1.0.1"
Expand Down
25 changes: 10 additions & 15 deletions examples/react-mobservable/readme.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# React TodoMVC Example
# React + MOBservable TodoMVC Example

> React is a JavaScript library for creating user interfaces. Its core principles are declarative code, efficiency, and flexibility. Simply specify what your component looks like and React will keep it up-to-date when the underlying data changes.
> _[React - facebook.github.io/react](http://facebook.github.io/react)_
> MOBservable is light-weight stand-alone library to create reactive primitives, functions, arrays and objects. Its goal is to make developers happy and productive. It loves React.
> _[MOBservable](https://github.com/mweststrate/MOBservable)_
## Learning React

Expand All @@ -13,26 +16,18 @@ Here are some links you may find helpful:

* [Documentation](http://facebook.github.io/react/docs/getting-started.html)
* [API Reference](http://facebook.github.io/react/docs/reference.html)
* [Blog](http://facebook.github.io/react/blog/)
* [React on GitHub](https://github.com/facebook/react)
* [Support](http://facebook.github.io/react/support.html)

Articles and guides from the community:

* [Philosophy](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood)
* [How is Facebook's React JavaScript library](http://www.quora.com/React-JS-Library/How-is-Facebooks-React-JavaScript-library)
* [React: Under the hood](http://www.quora.com/Pete-Hunt/Posts/React-Under-the-Hood)
## Learning MOBservable

Get help from other React users:
The examples in the documentation are probably the best place to get started.

* [React on StackOverflow](http://stackoverflow.com/questions/tagged/reactjs)
* [Mailing list on Google Groups](https://groups.google.com/forum/#!forum/reactjs)
*
_If you have other helpful links to share, or find any of the links above no longer work, please [let us know](https://github.com/tastejs/todomvc/issues)._
* [MOBservable readme with examples](https://github.com/mweststrate/MOBservable/blob/master/README.md)
* [Philosophy and in-depth analysis of MOBservable](https://www.mendix.com/tech-blog/making-react-reactive-pursuit-high-performing-easily-maintainable-react-apps/)

_If you have any questions or issues concerning this example of MOBservable in general, just file an issue at the [github repo](https://github.com/mweststrate/MOBservable/issues)_

## Running

The app is built with [JSX](http://facebook.github.io/react/docs/jsx-in-depth.html) and compiled at runtime for a lighter and more fun code reading experience. As stated in the link, JSX is not mandatory.

To run the app, spin up an HTTP server (e.g. `python -m SimpleHTTPServer`) and visit http://localhost/.../myexample/.
To run the app, spin up an HTTP server (e.g. `npm install -g servant && servant`) and visit http://localhost:3000

0 comments on commit 2e30cae

Please sign in to comment.