• This repository has been archived on 27/Feb/2019
  • Stars
    star
    98
  • Rank 334,642 (Top 7 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created about 9 years ago
  • Updated about 8 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Classical TodoMVC with React+Bacon.js

React+Bacon.js TodoMVC

Demonstration of the power and simplicity of React/FRP combo.

Motivation

Flux does a nice job when abstracting views from the "business logic". However Flux application tend to be complex and have a slight enterprise smell with all those actions dispatchers, stores and listeners. This introduces a lot of boilerplate and unnecessary complexity to the codebase.

This TodoMVC project demonstrates how to bypass all those intermediate steps and keep your views clean with the power of FRP.

How it works?

The most fundamental concept of Functional Reactive Programming (FRP) is the event stream. Streams are like arrays of events: they can be mapped, filtered, merged and combined.

The main idea is that every user action is just an event stream that is merged to the "application state" stream. Occurred events cause the application state to change. Finally the changed state object is then rendered at the top level of the application: React's virtual DOM does the rest!

This enables extremely simple React views. No callbacks and/or listener registrations are needed: the views only render what they are given and call synchronously the business logic interface on user actions. Business logic streams then propagate the state change back to views (which is again rendered stupidly). Nothing more is needed!

Application architecture

The essential component is the Dispatcher which is basically just an object of Bacon buses (Bacon.Bus is a stream that can have data pushed into it. Equals Rx's Subject).

const Bacon = require('baconjs')

module.exports = function() {
  const busCache = {}

  this.stream = function(name) {
    return bus(name)
  }
  this.push = function(name, value) {
    bus(name).push(value)
  }
  this.plug = function(name, value) {
    bus(name).plug(value)
  }

  function bus(name) {
    return busCache[name] = busCache[name] || new Bacon.Bus()
  }
}

This dispatcher enables the easy emitting and listening of user actions:

const Bacon       = require('baconjs'),
      Dispatcher  = require('./dispatcher')

const d = new Dispatcher()

module.exports = {
  toItemsProperty: function(initialItems, filterS) {
    const itemsS = Bacon.update(initialItems,
      [d.stream('remove')],           removeItem,
      [d.stream('create')],           createItem,
      [d.stream('addState')],         addItemState,
      [d.stream('removeState')],      removeItemState,
      [d.stream('removeCompleted')],  removeCompleteItems,
      [d.stream('updateTitle')],      updateItemTitle
    )

    return Bacon.combineAsArray([itemsS, filterS])
      .map(withDisplayStatus)

    function createItem(items, newItemTitle) {
      return items.concat([{id: Date.now(), title: newItemTitle, states: []}])
    }

    function removeItem(items, itemIdToRemove) {
      return R.reject(it => it.id === itemIdToRemove, items)
    }
    
    ... rest of the business logic here ...
  },

  // "public" methods

  createItem: function(title) {
    d.push('create', title)
  },

  removeItem: function(itemId) {
    d.push('remove', itemId)
  },
  
  ... rest of the public interface here ...
}

Business logic can be added to state stream easily with Bacon.combineTemplate:

const React   = require('react'),
      Bacon   = require('baconjs'),
      TodoApp = require('./todoApp'),
      todos   = require('./todos'),
      filter  = require('./filter')

const filterP = filter.toProperty(...intial filter state...),
      itemsP  = todos.toItemsProperty([], filterP)

const appState = Bacon.combineTemplate({
  items: itemsP,
  filter: filterP
})

appState.onValue((state) => {
  React.render(<TodoApp {...state} />, document.getElementById('todoapp'))
})

After that, using your business logic is dead simple:

const todos = require('./todos')
...
<button onClick={() => todos.removeCompleted()}>Clear completed</button>

And note that view does not need to know if action is synchronous or asynchronous: it's up to business logic to decide that.

Playing with the project

Feel free to clone the repository and start playing with the project:

git clone https://github.com/milankinen/react-bacon-todomvc.git
npm install
npm run watch
open "$(pwd)/index.html"

License

MIT

More Repositories

1

livereactload

Live code editing with Browserify and React
JavaScript
866
star
2

react-combinators

[NOT MAINTAINED] Seamless combination of React and reactive programming
JavaScript
93
star
3

stanga

[NOT MAINTAINED] The essential Cycling gear every Cyclist needs
JavaScript
81
star
4

ffux

[NOT MAINTAINED] Functional Reactive Flux for RxJS and Bacon.js
JavaScript
72
star
5

cuic

Clojure UI testing with Chrome
Clojure
34
star
6

relei

[NOT MAINTAINED] Simple, lightweight and reactive Relay client
JavaScript
25
star
7

culli

[NOT MAINTAINED] Cycle Utility Libraries for clean, well-structured and concise code
JavaScript
24
star
8

react-reactive-toolkit

[NOT MAINTAINED] React reactive programming toolkit without external dependencies
JavaScript
20
star
9

megablob

[NOT MAINTAINED] Utilities for React+Bacon MEGABLOB development
JavaScript
20
star
10

francis

Reactive programming with focus on developer friendly stream semantics, high performance and functional usage
TypeScript
16
star
11

ultimate-continuous-deployment

Shell
15
star
12

intellij-iceberg-theme

Dark blue color scheme for IntelliJ platform
Kotlin
8
star
13

livereactload-api

[OBSOLETE] API for LiveReactload browser integrations
JavaScript
7
star
14

cljs-rx

RxJS bindings for ClojureScript
JavaScript
4
star
15

mirex

Minimal reactive state primitives for JavaScript
TypeScript
3
star
16

imo

Opinionated Clojure(Script) code formatting
Clojure
3
star
17

francis.react

The official type-safe React bindings for Francis.
JavaScript
3
star
18

reapper

Thin React wrapper for ClojureScript
Clojure
1
star
19

clojure-ui-testing-demo

Codes for Reaktor #lisp meetup topic "Concise UI testing with Clojure"
JavaScript
1
star
20

c2w2c

C2W2C language model from my Master's Thesis
Python
1
star
21

replex

Clojure REPL extensions
Shell
1
star
22

dr-gan

[NOT MAINTAINED] DR-GAN model for person identification
Python
1
star
23

tunk

Optimizing JSX at transpilation time for greater performance
TypeScript
1
star
24

kefir-model

OBSOLETE DO NOT USE
JavaScript
1
star