Last active
January 31, 2018 16:46
-
-
Save jasonzoladz/28dc2850389f4881212abd89b576a6e4 to your computer and use it in GitHub Desktop.
Using Flow with RxJS for Unidirectional Data Using Props
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @flow | |
// USING PROPS | |
import Rx, { BehaviorSubject, Observable } from 'rxjs'; | |
import React, { Component } from 'react'; | |
import ReactDOM from 'react-dom'; | |
type AppState = { | |
n: number | |
} | |
// The props object contains the observable of app state | |
type AppProps = { | |
sc: Observable<AppState> | |
} | |
type Action | |
= Increment | |
| Decrement | |
| NoOp; | |
type Actions = Array<Action>; | |
type Increment = { kind: 'Increment' }; | |
type Decrement = { kind: 'Decrement' }; | |
type NoOp = { kind: 'NoOp' }; | |
type Effect | |
= AsyncIncrement | |
| AsyncDecrement | |
| AsyncNoOp; | |
type Effects = Array<Effect>; | |
type AsyncIncrement = { kind: 'AsyncIncrement' }; | |
type AsyncDecrement = { kind: 'AsyncDecrement' }; | |
type AsyncNoOp = { kind: 'AsyncNoOp' }; | |
function update(state: AppState, action: Action): AppState { | |
switch (action.kind) { | |
case 'Increment': return { n: state.n + 1}; | |
case 'Decrement': return { n: state.n - 1}; | |
default: return state; | |
} | |
} | |
function updateMany (updateFunction: (state: AppState, action: Action) => AppState) { | |
return function(initialState: AppState, actions: Actions): AppState { | |
return actions.reduce(updateFunction, initialState); | |
} | |
} | |
// All async functions map from an observable of effect to an observable of actions | |
function incrementAsync(effectsChannel: BehaviorSubject<Effect>): Observable<Actions> { | |
return effectsChannel.filter((eff: Effect) => eff.kind === "AsyncIncrement") | |
.debounceTime(1000) | |
.mapTo([{ kind: 'Increment'}]); | |
} | |
function decrementAsync(effectsChannel: BehaviorSubject<Effect>): Observable<Actions> { | |
return effectsChannel.filter((eff: Effect) => eff.kind === "AsyncDecrement") | |
.debounceTime(1000) | |
.mapTo([{ kind: 'Decrement'}]); | |
} | |
let actionsChannel: BehaviorSubject<Actions> = new BehaviorSubject( [ {kind: 'NoOp'} ] ); | |
let effectsChannel: BehaviorSubject<Effect> = new BehaviorSubject( {kind: 'AsyncNoOp'} ) | |
// Create a single observable of actions from your actions and effects | |
let allActions: Observable<Actions> = actionsChannel.merge( | |
incrementAsync(effectsChannel), | |
decrementAsync(effectsChannel) | |
) | |
const initialState: AppState = {n: 0}; | |
let stateChannel: Observable<AppState> = allActions.scan(updateMany(update), initialState); | |
let initialProps = | |
{ sc: stateChannel } | |
class App extends Component { | |
props: AppProps; | |
state: AppState; | |
constructor(p: AppProps){ | |
super(p); | |
this.state = { n: 0 } | |
// THIS IS WHERE THE MAGIC HAPPENS | |
this.props.sc.subscribe( | |
(st: AppState) => this.setState(st) | |
); | |
} | |
// NOTE: YOU CAN AVOID A RE-RENDER OF THE KIDS BY judicious use of `componentShouldUpdate` | |
render(){ | |
return( | |
<div> | |
<TwoButtons {...this.state}/> | |
</div> | |
); | |
} | |
} | |
function TwoButtons(props: AppState): React.Element<{}>{ | |
return( | |
<div> | |
<button onClick={() => effectsChannel.next({kind: 'AsyncIncrement'})}>DebouncedIncrement</button> | |
<button onClick={() => effectsChannel.next({kind: 'AsyncDecrement'})}>DebouncedDecrement</button> | |
{Paragraph(props)} | |
</div> | |
); | |
} | |
function Paragraph(props: AppState): React.Element<{}>{ | |
return( | |
<p>The current app state is: {props.n}</p> | |
); | |
} | |
function main(): void { | |
ReactDOM.render(<App {...initialProps}/>, document.getElementById('app')); | |
} | |
main(); | |
// Lats viewed January 31, 2018 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is an example of how one might use RxJS to manage application state for a React Native app. For a browser-based front end, PureScript is (in my opinion) a better choice than Flow.