State in reactatron uses resources. Resources are just an Rx stream with an initial value. They can also optionally have an emit
method allowing you to manipulate the resource asynchronously.
This is ideal for rendering in react because you can bind any component to any number of resources anywhere in your VDOM tree. The resource stream is subscribe to upon mounting that component, the component is re-rendered anytime the stream is published to (buffered by requestAnimationFrame).
If multiple components are mounted and therefore subscribed to the same resource they, by default, share that resource. Resources can also be configured to have independent state per subscriber.
Resources can be setup and torn down when the number of subscriber moves to and from 0
Building a simple resource from scratch:
// resources/location.js
import EventEmitter from 'events'
import Rx from 'rx-dom'
const getLocation = () => {
return {
path: window.location.pathname,
params: searchToParams(window.location.search),
}
}
// const = Rx.Subject.startWith(getLocation())
const location = Rx.DOM.pushState(window)
.map(getLocation)
.startWith(getLocation()) // setting up the initial state
// adding upstream events
const events = new MyEmitter
location.emit = events.emit.bind(events)
// defining event handlers
events.on('setLocation', payload => {
history.pushState(…)
})
export default location
Using Reactatron.createResource
:
// resources/transfers.js
import Reactatron from 'reactatron'
const transfers = Reactatron.createResource({
// run once as your first 1 subscriber subscribes
setup: () => {
},
// run after your last subscriber dismisses
teardown: () => {
},
// run anytime you gain a new subscriber
onSubscribe: () => {
},
// run anytime a subscriber dismisses
onDismiss: () => {
},
static events = {
reload: (payload) => {
putio.transfers().then(transfers => {
this.state.transfers = transfers
this.publish()
})
},
addTransfer: (payload) => {
}
}
})
// transfers is an Rx stream
// it also has an emit method
transfers.subscribe
transfers.emit
// any object that works like a stream with an optional emit method
// will work as a resource
what if we don't combine all of our application state into one large object and instead we subscribe to streams as part of our react tree
we could even have these streams start the initial data load process when first subscribed to. that lets us avoid the annoying fireing of an initial load content event on componentWillMount.
something like this
// resources/route.js
import Rx from 'rx-dom'
const route$ = Rx.DOM.pushState(window)
.map(getRoute)
.startWith(getRoute())
export { route$ }
const getRoute = () => { … }
// components/Router.js
import { bindToResources } from 'reactatron'
import { route$ } from '../resources/route'
Router = (props) => {
if (props.route === 'HomePage') return <HomePage />
if (props.route === 'AboutPage') return <AboutPage />
return <PageNotFound />
}
Router = bindToResources(Router, {
route: route$
})
export default Router
// bindToResources.js
class ResourceBinding extends Reacts.Component {
constructor(){
this.state = null
// subscribe to streams (combineLatest?) ???
}
// componentWillMount(){
// // subscribe to streams (combineLatest?)
// }
componentWillUnmount(){
// unsubscribe from streams
}
render(){
// state should never be null because the stream subscription callback should fire synchronously allowing state to be set to the streams initial value before the first render. (hopefully)
if (this.state === null) return null;
const props = Object.assign({}, this.props.props, this.state)
<this.component {...props} \/>
}
}
const bindToResources = (component, resources) => {
return (props) => {
return <ResourceBinding
props={props}
component={component}
resources={resources}
\/>
}
}
-
state as a stream
-
react component can be bound to resources
- so they're rerendered when the stream updates
-
resource streams must provide an initial value
-
resources can be turned on or off based on active subscribers
- some setup and teardown hooks would be nice here
-
resources are really a two streams
import { stream as transfers$ } from '../resources/stream'
const TransfersPage = (TransfersPage, )
- provide initial state
- handle events -> change state
- maintains own internal state?
- nothing can change the resource but its own handling of events
- resources could intercommunicate via events
- for example the torrent search resource can fire a reloadTransfers event just like the view layer would
How the fuck do I want my state to work? Nothing feels right.
events -> actions --> stateChange -> render
DOMEvent(click)->AppEvent(addToto)->appStateChange->reactRerender
PushState->AppEvent(locationChange)->appStateChange->reactRerender
- provide initial state
- handle events -> change state
- maintains own internal state?
- nothing can change the resource but its own handling of events
- resources could intercommunicate via events
- for example the torrent search resource can fire a reloadTransfers event just like the view layer would
Maybe we have have the <StateProvider>{child}</StateProvider>
component usable anywhere?
<StateProvider keys={'route'}>
{child}
</StateProvider>
<StateProvider keys={'todos'}>
<Todos />
</StateProvider>
const ReactComponent => (props){
StateProvider( state =>
<TodosList todos={state.todos} {...props} \/>
)
}
in order to only render parts of the vtree when state changes we need to be able to subscribe to parts of the tree.
maybe we can do this on a per-resource basis? each resource is a top level key in the store and they can only update themselves so we can know when each key is changed by when the resource publishes a new stream value.
how does redux do this?
maybe we shouldn't push state into one object?