Search code examples
reactjsreact-reduxactionhowler.js

Instantiate a new Howler Object in Action Creators and dispatch actions on events


So I'm trying to build a React.js + Redux audio player. I'm using the Howler.js Library to process audio from an API. I'm dispatching actions from the player control buttons and from other component such as a track list component. When the user clicks on a track in the lis, I'm dispatching a track object to the Player Reducer. I instantiate a new Howl Object here which works but there is some issues here :

  • Because Howler is fetching the data from an API, this is an async task and it's an anti pattern. I know that, but here I'm not triggering events from Howler so it works, but it's a bit buggy.
  • I can't use event triggers because it would be async and it's a bad pratice to dispatch actions from the Reducer.

What I would like :

  • Instanciate a new Howler Object in my Action Creator.
  • Use the event triggers, e.g : onload() or onloaderror(), to dispatch an other action to tell the player that Howler is ready to play a song or if there was an error.

The code :

Action Creator

export const PLAYER_INITIALIZE = 'PLAYER_INITIALIZE'
export const initialize = () => {
    return {
        type: PLAYER_INITIALIZE,
    }
}

export const PLAYER_SET_TRACK = 'PLAYER_SET_TRACK'
export const setTrack = (trackId) => {
    return {
        type: PLAYER_SET_TRACK,
    }
}

export const PLAYER_PLAY_TRACK = 'PLAYER_PLAY_TRACK'
export const playTrack = () => {
    return {
        type: PLAYER_PLAY_TRACK,
    }
}

Reducer

function PlayerReducer(state = initialPlayerState, action) {
    switch (action.type) {

        case PlayerActions.PLAYER_SET_TRACK:
            if (state.audioObj.state() != 'unloaded')
                state.audioObj.unload();
            return {
                ...state,
                audioObj: new Howl({
                    src: API.API_STREAM_TRACK + action.trackId,
                    html5: true,
                    preload: true,
                    onload: () => {
                        console.log("Track loaded succesfully.");
                    },
                    onloaderror: (id, err) => {
                        console.error("Load Error : " + err);
                    }
                }),
                trackMetadata: action.trackMetadata
            };

        case PlayerActions.PLAYER_PLAY_TRACK:
            state.audioObj.play();
            return {
                ...state,
                isPlaying: true,
            };

      [...]

My idea is to instanciate Howler like this :

new Howler({
    [ ... some options ],
    onload: () => {
        dispatch({
            type: PLAYER_LOAD_SUCCESS
        });
    },
    onloaderror: () => {
        dispatch({
            type: PLAYER_LOAD_ERROR
        });
    }
});

How can I edit this code to create a new Howl Object correctly in the Action Creator and pass it to the player on each track that I want to listen?

Can you give me an example ?

Thanks.


Solution

  • You can add redux-thunk to your app and then be able to dispatch multiple actions from a single action creator. After adding redux-thunk, your action could look like the following:

    export const setTrack = (trackId) => {
      (dispatch, getState) => {
        const { audioObj } = getState();
        if (audioObj != null) {
          audioObj.unload();
        }
    
        dispatch({
          audioObj:  new Howler({
              [ ... some options ],
              onload: () => {
                  dispatch({
                      type: PLAYER_LOAD_SUCCESS
                  });
              },
              onloaderror: () => {
                  dispatch({
                      type: PLAYER_LOAD_ERROR
                  });
              }
          }),
          type: PLAYER_SET_TRACK
      });
    }
    

    you can move your other async and Howler work into action creators this way and make your reducer only concerned with storing information.