Skip to content

Instantly share code, notes, and snippets.

@jasonzoladz
Last active January 31, 2018 16:46
Show Gist options
  • Save jasonzoladz/28dc2850389f4881212abd89b576a6e4 to your computer and use it in GitHub Desktop.
Save jasonzoladz/28dc2850389f4881212abd89b576a6e4 to your computer and use it in GitHub Desktop.
Using Flow with RxJS for Unidirectional Data Using Props
// @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
@jasonzoladz
Copy link
Author

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.

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