Skip to content

Instantly share code, notes, and snippets.

@justinwoo
Last active September 29, 2022 08:29
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save justinwoo/0f4b2a96aebbe3293612 to your computer and use it in GitHub Desktop.
Save justinwoo/0f4b2a96aebbe3293612 to your computer and use it in GitHub Desktop.
React 0.14 function components and a fake Elm Architecture written with RxJS subjects (instead of Signal.mailbox). Inspired by Dan's tweet: https://twitter.com/dan_abramov/status/648517117176745984 ((i guess this is pretty ugly so please don't take it super seriously))
import React from 'react';
/**
* Counter.js
*
* exposes:
* init
* update
* view
*/
// type alias Model = Int
// init : Int -> Model
export function init(count) {
return count;
}
//type Action = Increment | Decrement
let INCREMENT = 'INCREMENT';
let DECREMENT = 'DECREMENT';
//type alias ActionWrapper =
// { type : Action
// , ... }
export function update(actionWrapper, model) {
switch (actionWrapper.type) {
case INCREMENT:
return model + 1;
break;
case DECREMENT:
return model - 1;
break;
}
return model;
}
function onClick(address, action) {
return function () {
address.onNext({
type: action
});
}
}
export function view(address, model, renderKey) {
return (
<div key={renderKey}>
<button onClick={onClick(address, DECREMENT)}>-</button>
<h2>{model}</h2>
<button onClick={onClick(address, INCREMENT)}>+</button>
</div>
);
}
import React from 'react';
import * as Counter from './Counter';
/**
* CounterList.js
*
* exposes:
* init
* update
* view
*/
//type alias ID = Int
//type alias CounterUnit = [ ID, Counter.Model ]
//type alias Model =
//{ counters : List CounterUnit
//, nextID : ID
//}
// no freaking real tuples in javascript
//init : Model
export let init = {
counters: [],
nextID: 0
};
//type Action
//= Insert
//| Remove
//| Modify ID Counter.Action
let INSERT = 'INSERT';
let REMOVE = 'REMOVE';
let MODIFY = 'MODIFY';
//type alias ActionWrapper =
// { type : Action
// , ... }
export function update(actionWrapper, model) {
switch (actionWrapper.type) {
case INSERT:
let newCounter = [model.nextID, Counter.init(0)];
let newCounters = model.counters.slice()
newCounters.push(newCounter);
return Object.assign({}, model, {
counters: newCounters,
nextID: model.nextID + 1
});
break;
case REMOVE:
return Object.assign({}, model, {
counters: model.counters.slice(0, model.counters.length - 1)
});
break;
case MODIFY:
let updateCounter = function (counter) {
let [counterID, counterModel] = counter;
if (counterID === actionWrapper.id) {
return [counterID, Counter.update(actionWrapper.counterAction, counterModel)];
} else {
return counter;
}
};
return Object.assign({}, model, {
counters: model.counters.map(updateCounter)
});
break;
}
return model;
}
function onClick(address, action) {
return function () {
address.onNext({
type: action
});
}
}
/**
* Got better ideas on how to port this from Elm? Give me a shout!
*
* viewCounter : Signal.Address Action -> (ID, Counter.Model) -> Html
* viewCounter address (id, model) =
* Counter.view (Signal.forwardTo address (Modify id)) model
*/
function viewCounter(address) {
return function (counter) {
let [counterID, counterModel] = counter;
let forwardAddress = new Rx.Subject();
forwardAddress.subscribe(function (action) {
address.onNext({
type: MODIFY,
id: counterID,
counterAction: action
});
});
return Counter.view(forwardAddress, counterModel, counterID);
};
}
//view : Signal.Address Action -> Model -> Html
export function view(address, model) {
let counters = model.counters.map(viewCounter(address));
return (
<div>
<button onClick={onClick(address, REMOVE)}>Remove</button>
<button onClick={onClick(address, INSERT)}>Add</button>
{counters}
</div>
);
}
import 'babel-core/polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import start from './utils/StartApp';
import {
init,
update,
view
} from './CounterList';
// have to mount this ourselves since main isn't auto mounted by a runtime
start({
model: init,
view,
update
}).subscribe(function (view) {
ReactDOM.render(view, document.getElementById('app'));
});
import Rx from 'rx';
/**
* type alias Config model action =
* { model : model
* , view : Signal.Address action -> model -> Html
* , update : action -> model -> model
* }
*/
/**
* start : Config model action -> Signal Html
*/
export default function start(config) {
let address = new Rx.Subject();
let actions = Rx.Observable.just(config.model).merge(address);
let model = actions.scan(function (model, action) {
return config.update(action, model);
});
return model.map(function (model) {
return config.view(address, model);
});
}
@RoyalIcing
Copy link

Oh man love the use of RxJS!

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