Skip to content

Instantly share code, notes, and snippets.

@orodio
Last active November 27, 2015 02:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save orodio/ec46340dda913fe17d25 to your computer and use it in GitHub Desktop.
Save orodio/ec46340dda913fe17d25 to your computer and use it in GitHub Desktop.
How we currently redux.

every event type gets an aggregate file. As declaratively as possible we describe what an action does.

// src/aggregates/poll_playlist_response.js

import { GET } from "../tools/http";
import { curryDispatch } from "../tools/curry_dispatch";

export var type = "POLL_PLAYLIST_RESPONSE";

export var shape = {
  type,
  videos: Array,
};

export var pollForPlaylist = ( collectionId, gid) => dispatch => {
  GET(`${ __ROOT__ }/api/v1/collections/${ collectionId }?resource_id=${ gid }`)
    .then(res => dispatch({
      type,
      videos: res.data.resources,
    }))
};

export var actions = curryDispatch({ pollForPlaylist })

export var reducer = (state, { videos }) => ({
  ...state,
  playlist: {
    ...state.playlist,
    videos,
  }
});

we collect these aggregates together, and validate that they export a reducer, type, and shape. validateAggreates will throw a descriptive error telling our juniors how to make their aggregate conform to our aggregate standard.

// src/aggregates/__aggregates.js
import { validateAggregates } from "../tools/validate_aggregates"
import * as poll_playlist_response from "./poll_playlist_response";

export default validateAggregates([
  // ... other aggregates
  poll_playlist_response,
  // ... other aggregates
])

The reducer we pass into Reduxs createStore function builds a map of key function pairs, where the key is the exported type paird with the exported function from the aggregate. In the near future we will be checking the shape in here as well against the shape defined by the aggregate, if the shape is wrong we will give a descriptive error in devmode. The step after that will also be adding a declarative permission to the aggregate that will be validated in here as well. We have used this pattern in some of our experimental event sourced node applications and it worked quite well.

// src/aggregates/__reducer.js
import initial_state from "./__initial_state";
import aggregates from "./__aggregates";
import { isDevMode } from "../tools/dev";

var reducers = aggregates.reduce((acc, { type, reducer }) => ({ ...acc, [type]: reducer }), {})

export default (state, action) => {
  if (!state) state = initial_state;
  var { type } = action;
  if (!!reducers[type]) {
    // validate shape will go here
    // validate permission will go here
    return reducers[type](state, action);
  }
  if (isDevMode() && type !== "@@redux/INIT") console.error(`%c-- No Matching Action Found For: ${ type } --`, "color:tomato;font-weight:bold;")
  return state;
}

Our command components in react are your standard connected react-redux components, with the exception of binding the dispatch function to actions

// components/PlaylistViewer/index.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { actions as playlistActions } from "../../aggregates/poll_playlist_response";
import PlaylistItem from "./PlaylistItem";

var stateToProps = state => ({
  videos: state.playlist.videos,
})

var actionsToProps = dispatch => ({
  actions: playlistActions(dispatch)
})

@connect(stateToProps, actionsToProps)
export default class PlaylistViewer extends Component {
  static propTypes = { /* ... */ }
  
  componentWillMount () {
    var {
      gid,
      collectionId,
      actions: { pollForPlaylist }
    } = this.props;

    pollForPlaylist(collectionId, gid)
  }

  render () {
    var {
      videos,
    } = this.props;

    return  <div className="PlaylistViewr">
              { videos.map(PlaylistItem) }
            </div>
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment