0
votes

I'm trying to create an Epic that queues actions to be dispatched, in this case, the actions are messages to be displayed with a snackbar so that, for example, three errors happened almost simultaneously, and I want to display the three error messages with a SnackBar, the Epic should dispatch an action to display the first one for 3 seconds, then display the second one for 3 seconds and then the third one for 3 seconds.

Also if one of the snackbars gets closed, the second one in the "queue" should be displayed and so on. What I'm doing at the moment is dispatching the action that displays the message in Epics that catch the errors, then I'm creating Epics dispatch the action to close the snackbar after 3 seconds of delay, with the action CLOSE_SNACKBAR. Which methods should I use in order to achieve this?

A basic Epic I've implemented looks like this, basically the action that changes the state from the snackbar to open and displays the error message is dispatched from another epic, the one that catches the error, and then another Epic dispatches the action to close the snackbar after 3 seconds, but I haven't figured out how to do this queueing kind of action so that each message can be displayed for 3 seconds each, right now if an error occurs right after the first one, the second message will be displayed without waiting the 3 seconds after the previous message. Here are some basic examples of my epics (Ignore de request part, ):

const getUserEpic = (action$, store) => (
    action$.ofType(actionTypes.DATA_REQUESTED)
        .switchMap(action => {
            const { orderData, queryData, pagerData } = store.getState().usuario.list;
            const params = constructParams(queryData, pagerData, orderData);
            return Observable.defer(() => axios.get(`users`, { params }))
                .retry(NETWORK.RETRIES).mergeMap(response => {
                        Observable.of(actions.dataRequestSucceeded(response.data.rows))
                }).catch(error => Observable.concat(
                    Observable.of(actions.dataRequestFailed(error)),
                    Observable.of({
                        type:'DISPLAY_DATA_REQUEST_FAILED_MESSAGE',
                        open:true,
                        message:'Failed to get Users Data'
                    })
            ));
        })
)
const getRoleEpic = (action$, store) => (
    action$.ofType(actionTypes.DATA_REQUESTED)
        .switchMap(action => {
            const { orderData, queryData, pagerData } = store.getState().usuario.list;
            const params = constructParams(queryData, pagerData, orderData);
            return Observable.defer(() => axios.get(`role`, { params }))
                .retry(NETWORK.RETRIES).mergeMap(response => {
                        Observable.of(actions.dataRequestSucceeded(response.data.rows))
                }).catch(error => Observable.concat(
                    Observable.of(actions.dataRequestFailed(error)),
                    Observable.of({
                        type:'DISPLAY_DATA_REQUEST_FAILED_MESSAGE',
                        open:true,
                        message:'Failed to get Roles Data'
                    })
            ));
        })
)

This two epics do basically the seme, they are doing a GET request to the backend, but if they fail, they dispatch the action that opens the snackbar with an error message, and both error messages are different.

And this one is the epic that currently closes the Snackbar after 3 seconds:

const displayDataRequestFailedEpic = (action$, store) => (
    action$.ofType(actionTypes.DISPLAY_DATA_REQUEST_FAILED)
        .debounceTime(3e3)
        .switchMap( action => {
            return Observable.of({
                type:'CLOSE_SNACKBAR',
                open:false,
                message:''
            })
        })
)

Imagine I do both requests really fast and both of them fail. I want show all the errors that happened, one after another for 3 seconds each,

1
Can you reword/elaborate a bit more about what you're asking?jayphelps
I've reelaborated the question, hope the problem is clearer nowsgaseretto

1 Answers

0
votes

This hasn't been tested...

First, need to separate the action that displays messages from the action that is causing the display:

const getUserEpic = (action$, store) => (
    action$.ofType(actionTypes.DATA_REQUESTED)
        .switchMap(action => {
            const { orderData, queryData, pagerData } = store.getState().usuario.list;
            const params = constructParams(queryData, pagerData, orderData);
            return Observable.defer(() => axios.get(`users`, { params }))
                .retry(NETWORK.RETRIES).mergeMap(response => {
                        return Observable.of(actions.dataRequestSucceeded(response.data.rows))
                }).catch(error => Observable.of(actions.dateRequestFailed(error))

                // }).catch(error => Observable.concat(
                //     Observable.of(actions.dataRequestFailed(error)),
                //
                // this will be handled elsewhere
                //
                //     Observable.of({
                //         type:'DISPLAY_DATA_REQUEST_FAILED_MESSAGE',
                //         open:true,
                //         message:'Failed to get Users Data'
                //     })
            ));
        })
)

next, need to define some method that creates the DISPLAY_MESSAGE action from whatever action is triggering the message. Included in the action should be a unique id used to track which message needs to be closed.

Finally, use concatMap() to create and destroy the messages.

const displayEpic = action$ => action$
  .ofType(actionTypes.DATA_REQUEST_FAILED)
  .concatMap(action => {
    const messageAction = actions.displayMessage(action);
    const { id } = messageAction;
    return Rx.Observable.merge(
      Rx.Observable.of(messageAction),
      Rx.Observable.of(actions.closeMessage(id))
        .delay(3000)
        .takeUntil(action$
           .ofType(actionTypes.CLOSE_MESSAGE)
           .filter(a => a.id === id))
    );
  });