Redux: Document how people can benefit from observable spec interop

Created on 11 May 2016  ·  5Comments  ·  Source: reduxjs/redux

PR #1632 introduced observable spec to the store.

I'd be very interested in how I can use or benefit from that feature in my code, but I can't find any kind of documentation or example.

docs

Most helpful comment

React Redux does more than you might think. Just for fun, here's a back-of-the-envelope (in other words, I scribbled this down without actually running it, so there are _probably_ some mistakes) implementation of connect() using observables and rx-recompose:

import { PropTypes } from 'react'
import compose from 'recompose/compose'
import getContext from 'recompose/getContext'
import shallowEqual from 'recompose/shallowEqual'
import mapPropsStream from 'rx-recompose/mapPropsStream'
import { bindActionCreators } from 'redux'

const defaultMergeProps = (stateProps, dispatchProps, ownProps) => ({
  ...ownProps,
  ...stateProps,
  ...dispatchProps
})

const connect = (mapStateToProps, mapDispatchToProps, mergeProps = defaultMergeProps) =>
  compose(
    // Get store from context
    getContext({ store: PropTypes.object }),
    mapPropsStream(ownProps$ => {
      const store$ = ownProps$.pluck('store').first()

      // Subscribe to state updates
      const stateProps$ = store$
        .flatMap(store => store)
        .withLatestFrom(ownProps$, mapStateToProps)
        .distinctUntilChanged(null, shallowEqual) // Compare props to prevent unnecessary re-renders

      // Bind action creators
      const dispatch$ = store$.pluck('dispatch')
      let dispatchProps$
      if (typeof mapDispatchToProps === 'function') {
        dispatchProps$ = ownProps$
          .withLatestFrom(dispatch$, (props, dispatch) =>
            mapDispatchToProps(dispatch, props)
          )
      } else {
        const actionCreators = mapDispatchToProps
        dispatchProps$ = dispatch$
          .map(dispatch => bindActionCreators(actionCreators, dispatch))
      }

      // Combine into single stream of props
      return ownProps$.combineLatest(
        stateProps$, dispatchProps$, dispatch$
        (ownProps, stateProps, dispatchProps, dispatch) => ({
          ...mergeProps(stateProps, dispatchProps, ownProps)
          dispatch
        })
      )
    })
  )

And this is only a partial implementation :)

In most React apps, I'd say you're better off sticking with React Redux and dealing with the entire props object as a stream instead:

const enhance = compose(
  connect(selector, actionCreators), // React Redux
  mapPropsToStream(props$ => {
    // Selected state is part of props. Go crazy.
    return childProps$
  })
)

const EnhancedComponent = enhance(BaseComponent)

All 5 comments

As I understand it, it's literally just to make a Redux store act as an observable source if you already happen to be using observables in your project. If you're not using RxJS or Bacon or similar, it's irrelevant for you.

Parts of my app are pulling in RxJS so I'm interested if I could avoid react-redux and just use rx instead and possibly have a cleaner implementation. Or should I better stick with react-redux?

I'm familiar with Rx, but I don't know how to efficiently mix it with Redux.

react-redux has some non-trivial optimizations but you can use Rx just fine with a right combination of distinctUntilChanged, etc, I think. Give both a try and see what sticks? 😉

React Redux does more than you might think. Just for fun, here's a back-of-the-envelope (in other words, I scribbled this down without actually running it, so there are _probably_ some mistakes) implementation of connect() using observables and rx-recompose:

import { PropTypes } from 'react'
import compose from 'recompose/compose'
import getContext from 'recompose/getContext'
import shallowEqual from 'recompose/shallowEqual'
import mapPropsStream from 'rx-recompose/mapPropsStream'
import { bindActionCreators } from 'redux'

const defaultMergeProps = (stateProps, dispatchProps, ownProps) => ({
  ...ownProps,
  ...stateProps,
  ...dispatchProps
})

const connect = (mapStateToProps, mapDispatchToProps, mergeProps = defaultMergeProps) =>
  compose(
    // Get store from context
    getContext({ store: PropTypes.object }),
    mapPropsStream(ownProps$ => {
      const store$ = ownProps$.pluck('store').first()

      // Subscribe to state updates
      const stateProps$ = store$
        .flatMap(store => store)
        .withLatestFrom(ownProps$, mapStateToProps)
        .distinctUntilChanged(null, shallowEqual) // Compare props to prevent unnecessary re-renders

      // Bind action creators
      const dispatch$ = store$.pluck('dispatch')
      let dispatchProps$
      if (typeof mapDispatchToProps === 'function') {
        dispatchProps$ = ownProps$
          .withLatestFrom(dispatch$, (props, dispatch) =>
            mapDispatchToProps(dispatch, props)
          )
      } else {
        const actionCreators = mapDispatchToProps
        dispatchProps$ = dispatch$
          .map(dispatch => bindActionCreators(actionCreators, dispatch))
      }

      // Combine into single stream of props
      return ownProps$.combineLatest(
        stateProps$, dispatchProps$, dispatch$
        (ownProps, stateProps, dispatchProps, dispatch) => ({
          ...mergeProps(stateProps, dispatchProps, ownProps)
          dispatch
        })
      )
    })
  )

And this is only a partial implementation :)

In most React apps, I'd say you're better off sticking with React Redux and dealing with the entire props object as a stream instead:

const enhance = compose(
  connect(selector, actionCreators), // React Redux
  mapPropsToStream(props$ => {
    // Selected state is part of props. Go crazy.
    return childProps$
  })
)

const EnhancedComponent = enhance(BaseComponent)

If anyone actually does want to add a docs section on this, please comment here and let me know. Until then, closing due to inactivity.

Was this page helpful?
0 / 5 - 0 ratings