@sunny-g/rx-utils v2.0.0-beta.1
RxJS utils
Documentation in progress... :turtle:
High-level RxJS utils built to be used with bind operator (::, proposal stage).
Use library to increase readability and decrease code size for complex reactive dependencies.
API for state helpers is built around Functional Reducer pattern.
Notes
Observable is aliased as $ for brevity.
Sample 1
derived.flags
  .sample(derived.flags.map((fs) => fs.winGame)
  .filter(identity)).map((_) => (s) => assoc("ended", "win", s))
// =>
derived.flags
  ::atTrue("winGame")
  ::setState("ended", "win")Sample 2
$
  .combineLatest(src.navi, state2, derived.flags)
  .debounce(1)
  .map([src.navi, state2, derived.flags], gameView)
// =>
render(gameView, [src.navi, state2, derived.flags]),Sample 3
let errors = store(seeds, $.merge(
  state.map((s) => s.user.points).skip(1).map((x) => validate(Points)).map((p) => (s) => assocPath(["user", "points"], p, s)),
  state.map((s) => s.user.bonus).skip(1).map((x) => validate(Bonus)).map((p) => (s) => assocPath(["user", "points"], p, s))
))
// =>
let errors = store(seeds, $.merge(
  state::view("user.points").skip(1).map((x) => validate(Points))::toState("user.points"),
  state::view("user.bonus").skip(1).map((x) => validate(Bonus))::toState("user.bonus")
))Install
$ npm install babel-preset-es2016
$ npm install babel-plugin-syntax-function-bind
$ npm install babel-plugin-transform-function-bind
$ npm install rx
$ npm install ramda
$ npm install rx-utilsAdd to .babelrc:
{
  "plugins": [
    "syntax-function-bind",
    "transform-function-bind"
  ],
  "presets": [
    "es2016"
  ]
}Use
let {view} = require("rx-utils")
let userEmailStream = stateStream::view("user.email")API
$ u type for this variable (u for "upstream") is implied and omitted for brevity.
State
store
Canonical state reducer.
scan(...) + distinctUntilChanged() + shareReplay(1)
Example
update::store(...)history
Make observable of n last upstream values.
this + scan + distinctUntilChanged() + shareReplay(1)
Example
state::history(...)derive
Derive a state observable from a state observable.
combineLatest(...) + distinctUntilChanged() + shareReplay(1)
Example
derive(...)deriveN
Derive a state observable from state observables.
this + combineLatest(...) + distinctUntilChanged() + shareReplay(1)
Example
deriveN(...)Lensing
pluck
Make an observable of fragments of upstream values.
Like native .pluck with nested path support.
Example
intent::pluck("parentNode.dataset")pluckN
Make an observable of a fragment of upstream values.
Example
intent::pluckN(["parentNode.dataset1", "parentNode.dataset2"])view
Make an observable of a state fragment.
pluck(...) + distinctUntilChanged() + shareReplay(1)
Example
state::view("user.email")viewN
Make an observable of state fragments.
pluckN(...) + distinctUntilChanged() + shareReplay(1)
Example
state::viewN(["user.password", "user.passwordAgain"])toOverState : String, (u -> (sf -> sf)) -> $ (s -> s)
Apply function to upstream value, apply resulting function to state fragment.
Example
// createUser : $ User
createUser::toOverState("users", (u) => assoc(u.id, u))
// ==
// createUser : $ User
createUser.map((u) => (s) => assocPath(["users", u.id], u, s))toSetState : String, (sf -> sf) -> $ (s -> s)
Apply function to upstream value, replace state fragment with resulting value.
Example
// resetUsers : $ User
resetUsers::toSetState("users", (us) => map(..., us))
// ==
resetUsers.map((us) => (s) => assoc("users", map(..., us), s))overState : String, (sf -> sf) -> $ (s -> s)
Apply function to state fragment. Upstream value does not matter.
Example
// increment : $ Boolean
increment::overState("counter", (c) => c + 1)
// ==
increment.map((_) => (s) => assoc("counter", s.counter + 1, s))setState : String, v -> $ (s -> s)
Replace state fragment with a value. Upstream value does not matter.
Example
// reset : $ Boolean
resetForm::setState("form", seedForm)
// ==
resetForm.map((_) => (s) => assoc("form", seedForm, s))toState : String -> $ (s -> s)
Replace state fragment with upstream value.
Example
// changeUsername : $ String
changeUsername::toState("form.username"),
// ==
changeUsername.map((v) => (s) => assocPath(["form", "username"], v, s))Filtering & sampling
filterBy
Filter observable by another observable (true = keep).
Example
intent::filterBy(...)rejectBy
Filter observable by another observable (true = drop).
Example
intent::rejectBy(...)at
Pass upstream value futher if its fragment satisfies a predicate.
Example
flags::at(...)::overState(...)atTrue
Pass upstream value futher if its fragment is true.
Example
flags::atTrue(...)::overState(...)atFalse
Pass upstream value futher if its fragment is false.
Example
flags::atFalse(...)::overState(...)Other
render
Apply a function over observable values in a glitch-free way.
Example
let view$ = render(
  [ state$, props.get('displayText') ],
  ({ isLoading }, displayText) => (
    <div>
      {isLoading
        ? 'Loading...'
        : displayText
      }
    </div>
  ))