Skip to content

Instantly share code, notes, and snippets.

@vitalipe
Last active August 19, 2017 19:57
Show Gist options
  • Select an option

  • Save vitalipe/d1526ed377855d4aafff7fc29df62518 to your computer and use it in GitHub Desktop.

Select an option

Save vitalipe/d1526ed377855d4aafff7fc29df62518 to your computer and use it in GitHub Desktop.
mobx+immutable-basic-todo-mvc
// simple data manipulation functions, in a real app you will need more..
// Immutable.js the worst API possible for function composition, but they implemented
// enough Clojure functions to be actually useful for this example, so let's patch a few:
const _patchShittyDotNotation = _.curry((method, target, ...args) => target[method](...args), 3);
export const strictEquals = _.curry((x,y) => x === y, 2);
export const findIndex = (coll, item) => coll.findIndex(strictEquals(item));
export const filterNot = _patchShittyDotNotation("filterNot");
export const push = _patchShittyDotNotation("push"); // better to replace this with conj, because it's not polymorphic
export const mergeIn = _patchShittyDotNotation("mergeIn");
// this is the actual app code..
const {Record, List} = Immutable;
const TodoItem = Record({
title : "",
completed : false
});
// here we define a store with initial state & computed props
export const store = new StoreAtom({
todos : List([
new TodoItem({title : "create x"}),
new TodoItem({title: "create Y" , completed :true})]),
// mobx computed!
unfinishedTodoCount() {return this.todos.filter(todo => !todo.completed).size}
});
// actions are simply functions, but they can data as well, simply remove the "swapIn"
// call and push into a queue.. I personaly prefer the explicit approach.
// also note that all those are just basic CRUD actions, you can auto generate them..
export const todoItemDestroy = (item) => store.swapIn("todos", filterNot, strictEquals(item));
export const todoItemCreate = (title) => store.swapIn("todos", push, new TodoItem({title}));
export const todoItemUpdate = (item, data) => store.swapIn("todos", mergeIn, [findIndex(store.todos, item)], data);
export const todoItemToggle = (item) => todoItemUpdate(item, {completed : !item.get("completed")});
// StoreAtom is a mutable ref that holds the immuable data, it's basically a shitty
// version of reagent/atom.. but becuase mobx is awesome it works :)
export defualt function StoreAtom(schema) {
const _state = mobx.observable(schema);
// expose schema as getter
Object.defineProperties(this,
_(schema)
.reduce((props, v, k) => _.extend(
props, {[k] : {
get : function() { // yeah I know, it's lazy :P
return _.isFunction(v) ? _state[k]() : _state[k]}}
}), {}));
// kina like (swap! ...) in clojure. currently deep path is not supported, but it's easy to add
this.swapIn = mobx.action((path, action, ...args) => _state[path] = action(_state[path], ...args));
}
// basically copy-pasted from https://jsfiddle.net/mweststrate/wv3yopo0/
// just added a delete button and a way to add new todos
const {observable, computed} = mobx;
const {observer} = mobxReact;
const {Component} = React;
@observer
class TodoListView extends Component {
constructor(props) {
super(props);
this.state = {text : "new item"}
}
render() {
return <div>
<ul>
{this.props.todoList.todos.map(todo =>
<TodoView todo={todo} key={todo.id} />
)}
</ul>
<div>
Tasks left: {this.props.todoList.unfinishedTodoCount}
</div>
<hr />
<div>
Add Item:<input type="text"
value={this.state.text}
onChange={(e)=> this.setState({text : e.target.value})} />
<button
onClick={()=> todoItemCreate(this.state.text)}>add</button>
</div>
</div>
}
}
const TodoView = observer(({todo}) =>
<li>
<input
type="checkbox"
checked={todo.completed}
onClick={() => todoItemToggle(todo)}
/>
<label>{todo.title}</label>
<button onClick={()=> todoItemDestroy(todo)}>delete</button>
</li>
);
ReactDOM.render(<TodoListView todoList={store} />, document.getElementById('mount'));
@vitalipe
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment