Flux architecture - How to structure calling methods on a component


#1

I’ve run into another hurdle when it comes to knowing the best way to structure something in React (specifically using a Flux architecture). Here’s my scenario:

I’ve got several components on a page that are audio players. If I hit the play button, I want to use Dispatcher.dispatch('TRACK_PLAY', {id: track._id}) to send to the track store that a track should begin playing. I see two ways of carrying this out:

Option 1: In getMeteorData

  getMeteorData() {
    // this AppState var is set by a dispatch call elsewhere
    const trackId = AppState.get('artistTracks.playingTrackId');

    if (trackId === this.props.track._id) {
      this.play();
    }

    return {
      playingTrackId: trackId
    }
  },

  play() {
    // this.refs.audio points to an <audio> tag
    React.findDOMNode(this.refs.audio).play();
  },
  // ...

Option 2: In the store

Code to handle the play button click

  handlePlay() {
    Dispatcher.dispatch('TRACK_PLAY', {domRef: React.findDOMNode(this.refs.audio)});
  },

In the store

  'TRACK_PLAY': function (action) {
    action.domRef.play();
  }

I’m guessing option #2 might be the way to go, as getMeteorData may possibly not be a great place to do anything more than supply data to the component.

@luisherranz, any insight?


#2

The only caveat with option #2 is that the play/pause buttons are in one component, which contains another component that actually has the <audio> object. So I’d somehow have to trigger something within the actual player component to call the dispatch, because the parent that has the buttons wouldn’t be able to use this.refs.audio.


#3

You have multiple audio components? ideally you want only only have one audio onject for the site and just switch the file inside it depend on which audio you play, correct me if i am wrong.

I am actually doing a project with audio playback as well. What i did to track play is to just call Meteor.call(‘incPlayCount’, trackId) when the click is called.

So the audio object/component will take isPlaying as a prop and pause and play it self depend on the prop change. Same thing with its source.


#4

My app can have several audio tracks on a page, each with its own <audio> object, since it has to have a unique URL. So:

<TrackList>
  <AudioTrack />
  <AudioTrack />
  ...
</TrackList>

And then:

AudioTrack = React.createClass({
  render() {
    return <div>
      <div>(play and pause buttons here)</div>
      <AudioPlayer /> {/* AudioPlayer contains <audio> as well as a bunch of audio event handling */}
    </div>
  }
});

#5

What happens when user navigates away to a different page? does it still play?


#6

The only caveat with option #2 is that the play/pause buttons are in one component, which contains another component that actually has the object. So I’d somehow have to trigger something within the actual player component to call the dispatch, because the parent that has the buttons wouldn’t be able to use this.refs.audio.

That’s why you should only make one AudioPlayer that takes isPlaying as a prop, and updates it self depend on that prop change.

<AudioPlayer isPlaying={appState.isPlaying} />

class AudioPlayer {
 componentDidUpdate(prevProps, prevState){
  if(prevProps.isPlaying !== this.props.isPlaying) {
  this.updateIsPlaying();
 }
 updateIsPlaying(){
   if(!this.props.isPlaying){
     ReactDOM.findDOMNode(this).play()
   }else{
     ReactDOM.findDOMNode(this).pause()
   } 
 }
}

if you want to change track URL you just take url as a prop,

<AudioPlayer src={appState.currentTrackURL} />

 class AudioPlayer {
   componentDidUpdate(prevProps, prevState){
      if(prevProps.src !== this.props.src) {
      this.onSourceUpdate();
   }
   onSourceUpdate(){
      ReactDOM.findDOMNode(this).currentTime = 0;
      ReactDOM.findDOMNode(this).play();
   }
   render() {
      <audio src={this.props.src} />
   }
}

Now this player will manage playing itself when all you have to do is just change your app state. No need to make other components/stores direct do dom.Play() and Pause() on the element itself (which is whole point of react, components should really only takes care of rendering, especially when you are using Flux)


#7

Ahhh I see what you’re saying. So, just have one <audio> object that switches its src based on what’s playing. I like that approach!


#8

I don’t think passing the DOM element (option #2) is the way to go. Stores and Views (components) should not be aware of the existence of the other.

The communication between Stores and Views is done via State changes. So I would go with option #1 or like @sikanx suggested, with only one AudioTrack if that’s fine for your application.


#9

By the way, regarding option #1, it looks like people is saying that using getMeteorData in the real React component is not a good practice. Instead, create a parent component which encapsulates your real React component and pass everything it needs via props. The parent component only renders the real component with the correct props, like for example a this.props.isPlaying === true. That way, your real component is React-only and reusable even outside the Meteor world.


#10

I think you should decide if you want re-render’s based on changes in getMeteorData, or based on flux state.
There is not much control for you if you go getMeteorData way.


#11

Well, I’d prefer render to be triggered due to Flux store state change. But how else would render be triggered, since getMeteorData is the only React component method that is fired when a reactive variable changes?


#12

well, they are still reactive, getMeteorData just watch these and force re-render every time there is change.

you can wire up your own observers, or use something like https://github.com/AdamBrodzinski/meteor-flux-helpers