All Projects → ivan-kleshnin → reactive-states

ivan-kleshnin / reactive-states

Licence: other
Reactive state implementations (brainstorming)

Programming Languages

javascript
184084 projects - #8 most used programming language

Projects that are alternatives of or similar to reactive-states

Mobx Keystone
A MobX powered state management solution based on data trees with first class support for Typescript, support for snapshots, patches and much more
Stars: ✭ 284 (+456.86%)
Mutual labels:  reactive, state-management, frp
Stapp
Modular state and side-effects management for microfrontends
Stars: ✭ 88 (+72.55%)
Mutual labels:  reactive, state-management
Observable state
🔭 Flutter's State Manager for Reactive Apps in a Centralized and Predictable container.
Stars: ✭ 41 (-19.61%)
Mutual labels:  reactive, state-management
Ayanami
🍭 A better way to react with state
Stars: ✭ 129 (+152.94%)
Mutual labels:  reactive, state-management
Rxcombine
Bi-directional type bridging between RxSwift and Apple's Combine framework
Stars: ✭ 741 (+1352.94%)
Mutual labels:  reactive, frp
Platform
Reactive libraries for Angular
Stars: ✭ 7,020 (+13664.71%)
Mutual labels:  reactive, state-management
Bistate
A state management library for React combined immutable, mutable and reactive mode
Stars: ✭ 113 (+121.57%)
Mutual labels:  reactive, state-management
Outwatch
A purely functional and reactive UI framework
Stars: ✭ 376 (+637.25%)
Mutual labels:  reactive, frp
Kefir
A Reactive Programming library for JavaScript
Stars: ✭ 1,769 (+3368.63%)
Mutual labels:  reactive, frp
Mobx.dart
MobX for the Dart language. Hassle-free, reactive state-management for your Dart and Flutter apps.
Stars: ✭ 2,038 (+3896.08%)
Mutual labels:  reactive, state-management
Mag.js
MagJS - Modular Application Glue
Stars: ✭ 157 (+207.84%)
Mutual labels:  reactive, state-management
Reflex Platform
A curated package set and set of tools that let you build Haskell packages so they can run on a variety of platforms. reflex-platform is built on top of the nix package manager.
Stars: ✭ 602 (+1080.39%)
Mutual labels:  reactive, frp
Reatom
State manager with a focus of all needs
Stars: ✭ 567 (+1011.76%)
Mutual labels:  reactive, state-management
Reflex
Interactive programs without callbacks or side-effects. Functional Reactive Programming (FRP) uses composable events and time-varying values to describe interactive systems as pure functions. Just like other pure functional code, functional reactive code is easier to get right on the first try, maintain, and reuse.
Stars: ✭ 910 (+1684.31%)
Mutual labels:  reactive, frp
Xreact
reactive x react = xreact
Stars: ✭ 565 (+1007.84%)
Mutual labels:  reactive, frp
Sodium Typescript
Typescript/Javascript implementation of Sodium FRP (Functional Reactive Programming) library
Stars: ✭ 102 (+100%)
Mutual labels:  reactive, frp
Documentation
How does it all fit together?
Stars: ✭ 177 (+247.06%)
Mutual labels:  reactive, state-management
Reflex Dom
Web applications without callbacks or side-effects. Reflex-DOM brings the power of functional reactive programming (FRP) to the web. Build HTML and other Document Object Model (DOM) data with a pure functional interface.
Stars: ✭ 301 (+490.2%)
Mutual labels:  reactive, frp
Beedle
A tiny library inspired by Redux & Vuex to help you manage state in your JavaScript apps
Stars: ✭ 329 (+545.1%)
Mutual labels:  reactive, state-management
Swiftdux
Predictable state management for SwiftUI applications.
Stars: ✭ 130 (+154.9%)
Mutual labels:  reactive, state-management

Reactive states

RxJS is used for code samples but everything is supposed to be technology agnostic.


Reactive state is a state implemented by reducing values over time. There are two main questions to be asked for every particular library which provides a "state solution". Namely: the exact implementation of reducers and the number of them (one vs few vs many).

This two questions are completely orthogonal yet framework authors does not emphasize this fact enough. Saying "Redux architecture" the first person may have a "single store" concept in mind. The second one may imply "action-style reducers" while the third may assume a strict combination of both.

Let's start with possible reducer implementations.

Reducer implementations

Data reducer

let {Subject} = require("rx")

let update = new Subject() // update channel

let state = update 
  .startWith(0)          // initial value
  .scan((s, x) => s + x) // data reducer

state.subscribe(s => console.log("state:", s))

// TEST
update.onNext(1)  // +1
update.onNext(2)  // +2
update.onNext(3)  // +3
update.onNext(-3) // -3
update.onNext(-2) // -2
update.onNext(-1) // -1
state: 0
state: 1
state: 3
state: 6
state: 3
state: 1
state: 0

Now what if you want to reset state to some initial (seed) value? Obviously, you can't update.onNext(0) because it means s + 0. The best thing you can do is update.onNext((+/-)currentState) and you can guess how quick this becomes unreasonable.

Benefits

  • the simplest one

Drawbacks

  • fails to represent all but the simplest action sets
  • fails to represent nested state

Conclusion

  • is perfect for cases it satisfies ;)

Action Reducer

Redux way

let {Subject} = require("rx")

let update = new Subject() // update channel

let ADD = "+"
let SUBTRACT = "-"

let state = update 
  .startWith(0) 
  .scan((state, action) => {
    switch (action.type) {
      case ADD:
        return state + action.value
      case SUBTRACT:
        return state - action.value
      default:
        throw Error("unsupported action")
    }
  })

state.subscribe(s => console.log("state:", s))

// TEST
update.onNext({type: "+", value: 1}) // +1
update.onNext({type: "+", value: 2}) // +2
update.onNext({type: "-", value: 2}) // -2
update.onNext({type: "-", value: 1}) // -1
state: 0
state: 1
state: 3
state: 1
state: 0

Alternative way

let {Subject} = require("rx")

let update = new Subject() // update channel

let state = update 
  .startWith(0)
  .scan((state, action) => {
    switch (action[0]) {
      case "+":
        return state + action[1]
      case "-":
        return state - action[1]
      default:
        throw Error("unsupported action")
    }
  })

state.subscribe(s => console.log("state:", s))

// TEST
update.onNext(["+", 1]) // +1
update.onNext(["+", 2]) // +2
update.onNext(["-", 2]) // -2
update.onNext(["-", 1]) // -1
state: 0
state: 1
state: 3
state: 1
state: 0

Now what if we want to reset counter here? It's just a matter of adding a new switch branch.

Benefits

  • can describe arbitrarily complex action sets
  • reducer contains a list of possible actions
  • can represent nested state
  • easy to log actions

The most interesting benefit of this one is the ability to log actions easily. You just need to prepend a reducer with a logger as all you need is there (in the pipe). This comes in handy for scrapers where you need to log all pages you download, all documents you save, etc.

Drawbacks

  • can't extend reducer you don't control (need to create new one and compose them: expression problem)
  • reducer contains a list of possible actions (fast growing if / switch statement: expression problem)
  • incidental complexity (middlemen) (data structure kinda conveys a list of possible actions already)
  • need to edit multiple files to implement one action (action & reducer files)

Conclusion

  • are good for basic-to-medium action sets (?)
  • or if you need a no-brainer for logging

Functional Reducer

let {add, curry, flip, subtract} = require("ramda")
let {Subject} = require("rx")

// scanFn :: s -> (s -> s) -> s
let scanFn = curry((state, updateFn) => {
  if (typeof updateFn != "function" || updateFn.length != 1) {
    throw Error("updateFn must be a function with arity 1, got " + updateFn)
  } else {
    return updateFn(state)
  }
})

let update = new Subject() // update channel

let state = update 
  .startWith(0)
  .scan(scanFn)

state.subscribe(s => console.log("state:", s))

// TEST
update.onNext(add(1))            // +1
update.onNext(add(2))            // +2
update.onNext(add(3))            // +3
update.onNext(flip(subtract)(3)) // -3
update.onNext(flip(subtract)(2)) // -2
update.onNext(flip(subtract)(1)) // -1

Benefits

  • open action set (not limited by hardcoded actions)
  • composable actions (update3 = compose(update2, update1))

Drawbacks

  • open action set (can be viewed as drawback)
  • hard to log actions (stream of functions is obscure)

Conclusion

  • pairs nicely with currying
  • pairs nicely with lensing
  • is appropriate for most action sets out there (?)

Derived states

What is derived state? It's a state which is produced from other state (common or derived as well).
"So why is this not just a state? Or just a stream?" – you can ask. "Why we need additional terms besides MVC, MVI and whatnot?" Unfortunately, world is too complex for acronyms.

Form errors is an example of derived state. It's a state because it's renderable. You render input errors in the same way as input values. But there are several reasons to treat them differently.

  1. Minimal state size is desirable for serialization (transfer, etc.)
  2. Keep one source of truth (avoid unsync cases like y == f(x) /* by formula */ y != f(x) /* by fact */.
  3. Preserve reactivity (derivables are either passive or reactive).

This should detract you from the idea of mixing common and derived states in a single reducer. What options are left?

  1. Derived state is a stream (not usual but stateful one).
let derived = state.map((s) => {
  let d = ...
  // calculate every dependency
  return d
})
  1. Derived state is a record of streams.
let derived = {
  d1: state.map((s) => ...),
  d2: state.map((s) => ...),
}

In a single-stream version, state.counter increasing every second will trigger a recalculation of derived state every second. In a multi-stream version you can describe reactive dependencies per-field someFlag.debounce(10) but passing stuff between functions is encomplicated. You can also mix-n-match approaches:

let derived = {          // RECORDS OF STREAMS
  foo: fooStream,        // single-value stream
  bar: barStream,        // ...
  flags: state.map(...), // multi-value stream
}
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].