Skip to content

Instantly share code, notes, and snippets.

@jaredatron
Last active July 23, 2016 04:25
Show Gist options
  • Save jaredatron/3e4dfa044aa1791139459bfbae243e66 to your computer and use it in GitHub Desktop.
Save jaredatron/3e4dfa044aa1791139459bfbae243e66 to your computer and use it in GitHub Desktop.
Thoughts on State

State

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

One big state?

reduxjs/redux#1385

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}
    \/>
  }
}

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

Resources

  • 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

Rerender the entire vtree every time state changes?

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?

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