Skip to content

Instantly share code, notes, and snippets.

@yinghaochan
Created January 2, 2016 21:20
Show Gist options
  • Save yinghaochan/56ab2ece8a16b0e2bd8e to your computer and use it in GitHub Desktop.
Save yinghaochan/56ab2ece8a16b0e2bd8e to your computer and use it in GitHub Desktop.
Readme for redux/meteor

 

Technical details

An implementation of redux with meteor for full stack reactivity and unidirectional data flow. Components are designed to work in isolation, taking state from the redux store or from the database directly, ensuring drop-in code resuability across different views.

Data flow

The redux store is a singleton and stores almost all relevant data.

Local actions which don't reach the server

  • Skips the middleware and gets dispatched directly to the local reducer, changing the store

local actions which need to be shared

  • Uses thunk to send data to the server in an async call. Which might:
    • Dispatch a local action to optimistically change the store
  • Calls an async method (usually Meteor.call) to send data to the server
  • Upon change of the server DB, any trackers listening will dispatch actions with the new data.
  • The store will be changed from the db update, as the single source of truth (overwriting any local change)

 

DRY Data Flow example

!!insert image here

Requirements

We need to display slides on every device during a presentation, all of which are synced based on several rules:

  • svgs for the presentation are kept in the store, so slide changes are instant.
  • The projector always displays the slide which the presenter is on
  • The audience and view all previous slides that the presenter has visited, but not further.

Rendering slides

To render a slide in jsx, all views simply call the <Slide /> component from modules/sub_slide. This is the same across the presenter, presenter remote, projector and audience views.

  • sub_slide is also used to show the presenter the next slide in the deck, by passing in <Slide slideIndex={1+presenterIndex} />
  • this optional prop is also used to display thumbnails of all slides by displaying an array of <Slide slideIndex={i++} /> components.

Redux actions then are written to intelligently choose the appropriate slideIndex. Without an overriding index, the <Slide /> component is just:

<div dangerouslySetInnerHTML={() => ({__html: store.deck[slideIndex]})} />

Controlling the slides

Presenter or audience even call the same functions, increment() or decrement() from anywhere in the app, and the centralized logic in redux handles the rest.

This means that a simple presenter / audience / projector view and controller could be excatly the same and still work as intended.

  render: function () {
    const {increment, decrement, setIndex} = this.props

    return (
      <div>
        <div>
          <button onClick={() => increment()}> ++ </button>
          <button onClick={() => decrement()}> -- </button>
        </div>
        <Slide />
      </div>
      )
  }

We take DRY principles even further:

export function increment() {
  return setIndex(null, 1)
}

export function decrement() {
  return setIndex(null, -1)
}

both increment() or decrement() actually call one function, setIndex(), which also takes in a first argument for explicitly setting the slides to a particular index.

So all logic is in one single redux action:

// action to manually set index using the first arg
export function setIndex(index, operator) {
  return function(dispatch, getState){

    // get the desired index if !index
    if (index === null){   ...   }

    // check if out of bounds
    if (index < 0 || index >= show.numSlides){     ... break   }
    
    if (/* current user is the presenter */){
      // update DB, send info to everyone
    } 
      // increment currentIndex locally
      dispatch(setSlide(index))
      
  }
}

finally, we recieve all updates from the DB at one point:

// track the maxIndex and presenterIndex from the server
export function trackPresenter (id) {
  return Tracker.autorun(function (computation) {
    let show = Shows.findOne({_id: id})
    
      // update the store
      dispatch(setPresenter(show.presenterIndex))
      dispatch(setMax(show.maxIndex))
      
      if (show.ownerId === Meteor.userId()){
        // set the current slide to presenterIndex if current user is the presenter
        dispatch(setSlide(show.presenterIndex))
      }
    
  })
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment