[javascript] How to dispatch a Redux action with a timeout?

I have an action that updates the notification state of my application. Usually, this notification will be an error or info of some sort. I need to then dispatch another action after 5 seconds that will return the notification state to the initial one, so no notification. The main reason behind this is to provide functionality where notifications disappear automatically after 5 seconds.

I had no luck with using setTimeout and returning another action and can't find how this is done online. So any advice is welcome.

This question is related to javascript redux react-redux timeout

The answer is


Why should it be so hard? It's just UI logic. Use a dedicated action to set notification data:

dispatch({ notificationData: { message: 'message', expire: +new Date() + 5*1000 } })

and a dedicated component to display it:

const Notifications = ({ notificationData }) => {
    if(notificationData.expire > this.state.currentTime) {
      return <div>{notificationData.message}</div>
    } else return null;
}

In this case the questions should be "how do you clean up old state?", "how to notify a component that time has changed"

You can implement some TIMEOUT action which is dispatched on setTimeout from a component.

Maybe it's just fine to clean it whenever a new notification is shown.

Anyway, there should be some setTimeout somewhere, right? Why not to do it in a component

setTimeout(() => this.setState({ currentTime: +new Date()}), 
           this.props.notificationData.expire-(+new Date()) )

The motivation is that the "notification fade out" functionality is really a UI concern. So it simplifies testing for your business logic.

It doesn't seem to make sense to test how it's implemented. It only makes sense to verify when the notification should time out. Thus less code to stub, faster tests, cleaner code.


You can do this with redux-thunk. There is a guide in redux document for async actions like setTimeout.


This may be a bit off-topic but I want to share it here because I simply wanted to remove Alerts from state after a given timeout i.e. auto hiding alerts/notifications.

I ended up using setTimeout() within the <Alert /> component, so that it can then call and dispatch a REMOVE action on given id.

export function Alert(props: Props) {
  useEffect(() => {
    const timeoutID = setTimeout(() => {
      dispatchAction({
        type: REMOVE,
        payload: {
          id: id,
        },
      });
    }, timeout ?? 2000);
    return () => clearTimeout(timeoutID);
  }, []);
  return <AlertComponent {...props} />;
}

After trying the various popular approaches (action creators, thunks, sagas, epics, effects, custom middleware), I still felt that maybe there was room for improvement so I documented my journey in this blog article, Where do I put my business logic in a React/Redux application?

Much like the discussions here, I tried to contrast and compare the various approaches. Eventually it led me to introducing a new library redux-logic which takes inspiration from epics, sagas, custom middleware.

It allows you to intercept actions to validate, verify, authorize, as well as providing a way to perform async IO.

Some common functionality can simply be declared like debouncing, throttling, cancellation, and only using the response from the latest request (takeLatest). redux-logic wraps your code providing this functionality for you.

That frees you to implement your core business logic however you like. You don't have to use observables or generators unless you want to. Use functions and callbacks, promises, async functions (async/await), etc.

The code for doing a simple 5s notification would be something like:

_x000D_
_x000D_
const notificationHide = createLogic({_x000D_
  // the action type that will trigger this logic_x000D_
  type: 'NOTIFICATION_DISPLAY',_x000D_
  _x000D_
  // your business logic can be applied in several_x000D_
  // execution hooks: validate, transform, process_x000D_
  // We are defining our code in the process hook below_x000D_
  // so it runs after the action hit reducers, hide 5s later_x000D_
  process({ getState, action }, dispatch) {_x000D_
    setTimeout(() => {_x000D_
      dispatch({ type: 'NOTIFICATION_CLEAR' });_x000D_
    }, 5000);_x000D_
  }_x000D_
});_x000D_
    
_x000D_
_x000D_
_x000D_

I have a more advanced notification example in my repo that works similar to what Sebastian Lorber described where you could limit the display to N items and rotate through any that queued up. redux-logic notification example

I have a variety of redux-logic jsfiddle live examples as well as full examples. I'm continuing to work on docs and examples.

I'd love to hear your feedback.


It is simple. Use trim-redux package and write like this in componentDidMount or other place and kill it in componentWillUnmount.

componentDidMount() {
  this.tm = setTimeout(function() {
    setStore({ age: 20 });
  }, 3000);
}

componentWillUnmount() {
  clearTimeout(this.tm);
}

If you want timeout handling on selective actions, you can try the middleware approach. I faced a similar problem for handling promise based actions selectively and this solution was more flexible.

Lets say you your action creator looks like this:

//action creator
buildAction = (actionData) => ({
    ...actionData,
    timeout: 500
})

timeout can hold multiple values in the above action

  • number in ms - for a specific timeout duration
  • true - for a constant timeout duration. (handled in the middleware)
  • undefined - for immediate dispatch

Your middleware implementation would look like this:

//timeoutMiddleware.js
const timeoutMiddleware = store => next => action => {

  //If your action doesn't have any timeout attribute, fallback to the default handler
  if(!action.timeout) {
    return next (action)
  }

  const defaultTimeoutDuration = 1000;
  const timeoutDuration = Number.isInteger(action.timeout) ? action.timeout || defaultTimeoutDuration;

//timeout here is called based on the duration defined in the action.
  setTimeout(() => {
    next (action)
  }, timeoutDuration)
}

You can now route all your actions through this middleware layer using redux.

createStore(reducer, applyMiddleware(timeoutMiddleware))

You can find some similar examples here


A repository with sample projects

Current there are four sample projects:

  1. Writing Async Code Inline
  2. Extracting Async Action Creator
  3. Use Redux Thunk
  4. Use Redux Saga

The accepted answer is awesome.

But there is something missing:

  1. No runnable sample projects, just some code snippets.
  2. No sample code for other alternatives, such as:
    1. Redux Saga

So I created the Hello Async repository to add the missing things:

  1. Runnable projects. You can download and run them without modification.
  2. Provide sample code for more alternatives:

Redux Saga

The accepted answer already provides sample code snippets for Async Code Inline, Async Action Generator and Redux Thunk. For the sake of completeness, I provide code snippets for Redux Saga:

// actions.js

export const showNotification = (id, text) => {
  return { type: 'SHOW_NOTIFICATION', id, text }
}

export const hideNotification = (id) => {
  return { type: 'HIDE_NOTIFICATION', id }
}

export const showNotificationWithTimeout = (text) => {
  return { type: 'SHOW_NOTIFICATION_WITH_TIMEOUT', text }
}

Actions are simple and pure.

// component.js

import { connect } from 'react-redux'

// ...

this.props.showNotificationWithTimeout('You just logged in.')

// ...

export default connect(
  mapStateToProps,
  { showNotificationWithTimeout }
)(MyComponent)

Nothing is special with component.

// sagas.js

import { takeEvery, delay } from 'redux-saga'
import { put } from 'redux-saga/effects'
import { showNotification, hideNotification } from './actions'

// Worker saga
let nextNotificationId = 0
function* showNotificationWithTimeout (action) {
  const id = nextNotificationId++
  yield put(showNotification(id, action.text))
  yield delay(5000)
  yield put(hideNotification(id))
}

// Watcher saga, will invoke worker saga above upon action 'SHOW_NOTIFICATION_WITH_TIMEOUT'
function* notificationSaga () {
  yield takeEvery('SHOW_NOTIFICATION_WITH_TIMEOUT', showNotificationWithTimeout)
}

export default notificationSaga

Sagas are based on ES6 Generators

// index.js

import createSagaMiddleware from 'redux-saga'
import saga from './sagas'

const sagaMiddleware = createSagaMiddleware()

const store = createStore(
  reducer,
  applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(saga)

Compared to Redux Thunk

Pros

  • You don't end up in callback hell.
  • You can test your asynchronous flows easily.
  • Your actions stay pure.

Cons

  • It depends on ES6 Generators which is relatively new.

Please refer to the runnable project if the code snippets above don't answer all of your questions.


Redux itself is a pretty verbose library, and for such stuff you would have to use something like Redux-thunk, which will give a dispatch function, so you will be able to dispatch closing of the notification after several seconds.

I have created a library to address issues like verbosity and composability, and your example will look like the following:

import { createTile, createSyncTile } from 'redux-tiles';
import { sleep } from 'delounce';

const notifications = createSyncTile({
  type: ['ui', 'notifications'],
  fn: ({ params }) => params.data,
  // to have only one tile for all notifications
  nesting: ({ type }) => [type],
});

const notificationsManager = createTile({
  type: ['ui', 'notificationManager'],
  fn: ({ params, dispatch, actions }) => {
    dispatch(actions.ui.notifications({ type: params.type, data: params.data }));
    await sleep(params.timeout || 5000);
    dispatch(actions.ui.notifications({ type: params.type, data: null }));
    return { closed: true };
  },
  nesting: ({ type }) => [type],
});

So we compose sync actions for showing notifications inside async action, which can request some info the background, or check later whether the notification was closed manually.


Using Redux-saga

As Dan Abramov said, if you want more advanced control over your async code, you might take a look at redux-saga.

This answer is a simple example, if you want better explanations on why redux-saga can be useful for your application, check this other answer.

The general idea is that Redux-saga offers an ES6 generators interpreter that permits you to easily write async code that looks like synchronous code (this is why you'll often find infinite while loops in Redux-saga). Somehow, Redux-saga is building its own language directly inside Javascript. Redux-saga can feel a bit difficult to learn at first, because you need basic understanding of generators, but also understand the language offered by Redux-saga.

I'll try here to describe here the notification system I built on top of redux-saga. This example currently runs in production.

Advanced notification system specification

  • You can request a notification to be displayed
  • You can request a notification to hide
  • A notification should not be displayed more than 4 seconds
  • Multiple notifications can be displayed at the same time
  • No more than 3 notifications can be displayed at the same time
  • If a notification is requested while there are already 3 displayed notifications, then queue/postpone it.

Result

Screenshot of my production app Stample.co

toasts

Code

Here I named the notification a toast but this is a naming detail.

function* toastSaga() {

    // Some config constants
    const MaxToasts = 3;
    const ToastDisplayTime = 4000;


    // Local generator state: you can put this state in Redux store
    // if it's really important to you, in my case it's not really
    let pendingToasts = []; // A queue of toasts waiting to be displayed
    let activeToasts = []; // Toasts currently displayed


    // Trigger the display of a toast for 4 seconds
    function* displayToast(toast) {
        if ( activeToasts.length >= MaxToasts ) {
            throw new Error("can't display more than " + MaxToasts + " at the same time");
        }
        activeToasts = [...activeToasts,toast]; // Add to active toasts
        yield put(events.toastDisplayed(toast)); // Display the toast (put means dispatch)
        yield call(delay,ToastDisplayTime); // Wait 4 seconds
        yield put(events.toastHidden(toast)); // Hide the toast
        activeToasts = _.without(activeToasts,toast); // Remove from active toasts
    }

    // Everytime we receive a toast display request, we put that request in the queue
    function* toastRequestsWatcher() {
        while ( true ) {
            // Take means the saga will block until TOAST_DISPLAY_REQUESTED action is dispatched
            const event = yield take(Names.TOAST_DISPLAY_REQUESTED);
            const newToast = event.data.toastData;
            pendingToasts = [...pendingToasts,newToast];
        }
    }


    // We try to read the queued toasts periodically and display a toast if it's a good time to do so...
    function* toastScheduler() {
        while ( true ) {
            const canDisplayToast = activeToasts.length < MaxToasts && pendingToasts.length > 0;
            if ( canDisplayToast ) {
                // We display the first pending toast of the queue
                const [firstToast,...remainingToasts] = pendingToasts;
                pendingToasts = remainingToasts;
                // Fork means we are creating a subprocess that will handle the display of a single toast
                yield fork(displayToast,firstToast);
                // Add little delay so that 2 concurrent toast requests aren't display at the same time
                yield call(delay,300);
            }
            else {
                yield call(delay,50);
            }
        }
    }

    // This toast saga is a composition of 2 smaller "sub-sagas" (we could also have used fork/spawn effects here, the difference is quite subtile: it depends if you want toastSaga to block)
    yield [
        call(toastRequestsWatcher),
        call(toastScheduler)
    ]
}

And the reducer:

const reducer = (state = [],event) => {
    switch (event.name) {
        case Names.TOAST_DISPLAYED:
            return [...state,event.data.toastData];
        case Names.TOAST_HIDDEN:
            return _.without(state,event.data.toastData);
        default:
            return state;
    }
};

Usage

You can simply dispatch TOAST_DISPLAY_REQUESTED events. If you dispatch 4 requests, only 3 notifications will be displayed, and the 4th one will appear a bit later once the 1st notification disappears.

Note that I don't specifically recommend dispatching TOAST_DISPLAY_REQUESTED from JSX. You'd rather add another saga that listens to your already-existing app events, and then dispatch the TOAST_DISPLAY_REQUESTED: your component that triggers the notification, does not have to be tightly coupled to the notification system.

Conclusion

My code is not perfect but runs in production with 0 bugs for months. Redux-saga and generators are a bit hard initially but once you understand them this kind of system is pretty easy to build.

It's even quite easy to implement more complex rules, like:

  • when too many notifications are "queued", give less display-time for each notification so that the queue size can decrease faster.
  • detect window size changes, and change the maximum number of displayed notifications accordingly (for example, desktop=3, phone portrait = 2, phone landscape = 1)

Honnestly, good luck implementing this kind of stuff properly with thunks.

Note you can do exactly the same kind of thing with redux-observable which is very similar to redux-saga. It's almost the same and is a matter of taste between generators and RxJS.


The appropriate way to do this is using Redux Thunk which is a popular middleware for Redux, as per Redux Thunk documentation:

"Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters".

So basically it returns a function, and you can delay your dispatch or put it in a condition state.

So something like this is going to do the job for you:

import ReduxThunk from 'redux-thunk';

const INCREMENT_COUNTER = 'INCREMENT_COUNTER';

function increment() {
  return {
    type: INCREMENT_COUNTER
  };
}

function incrementAsync() {
  return dispatch => {
    setTimeout(() => {
      // Yay! Can invoke sync or async actions with `dispatch`
      dispatch(increment());
    }, 5000);
  };
}

I would recommend also taking a look at the SAM pattern.

The SAM pattern advocates for including a "next-action-predicate" where (automatic) actions such as "notifications disappear automatically after 5 seconds" are triggered once the model has been updated (SAM model ~ reducer state + store).

The pattern advocates for sequencing actions and model mutations one at a time, because the "control state" of the model "controls" which actions are enabled and/or automatically executed by the next-action predicate. You simply cannot predict (in general) what state the system will be prior to processing an action and hence whether your next expected action will be allowed/possible.

So for instance the code,

export function showNotificationWithTimeout(dispatch, text) {
  const id = nextNotificationId++
  dispatch(showNotification(id, text))

  setTimeout(() => {
    dispatch(hideNotification(id))
  }, 5000)
}

would not be allowed with SAM, because the fact that a hideNotification action can be dispatched is dependent on the model successfully accepting the value "showNotication: true". There could be other parts of the model that prevents it from accepting it and therefore, there would be no reason to trigger the hideNotification action.

I would highly recommend that implement a proper next-action predicate after the store updates and the new control state of the model can be known. That's the safest way to implement the behavior you are looking for.

You can join us on Gitter if you'd like. There is also a SAM getting started guide available here.


I understand that this question is a bit old but I'm going to introduce another solution using redux-observable aka. Epic.

Quoting the official documentation:

What is redux-observable?

RxJS 5-based middleware for Redux. Compose and cancel async actions to create side effects and more.

An Epic is the core primitive of redux-observable.

It is a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.

In more or less words, you can create a function that receives actions through a Stream and then return a new stream of actions (using common side effects such as timeouts, delays, intervals, and requests).

Let me post the code and then explain a bit more about it

store.js

import {createStore, applyMiddleware} from 'redux'
import {createEpicMiddleware} from 'redux-observable'
import {Observable} from 'rxjs'
const NEW_NOTIFICATION = 'NEW_NOTIFICATION'
const QUIT_NOTIFICATION = 'QUIT_NOTIFICATION'
const NOTIFICATION_TIMEOUT = 2000

const initialState = ''
const rootReducer = (state = initialState, action) => {
  const {type, message} = action
  console.log(type)
  switch(type) {
    case NEW_NOTIFICATION:
      return message
    break
    case QUIT_NOTIFICATION:
      return initialState
    break
  }

  return state
}

const rootEpic = (action$) => {
  const incoming = action$.ofType(NEW_NOTIFICATION)
  const outgoing = incoming.switchMap((action) => {
    return Observable.of(quitNotification())
      .delay(NOTIFICATION_TIMEOUT)
      //.takeUntil(action$.ofType(NEW_NOTIFICATION))
  });

  return outgoing;
}

export function newNotification(message) {
  return ({type: NEW_NOTIFICATION, message})
}
export function quitNotification(message) {
  return ({type: QUIT_NOTIFICATION, message});
}

export const configureStore = () => createStore(
  rootReducer,
  applyMiddleware(createEpicMiddleware(rootEpic))
)

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import {configureStore} from './store.js'
import {Provider} from 'react-redux'

const store = configureStore()

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

App.js

import React, { Component } from 'react';
import {connect} from 'react-redux'
import {newNotification} from './store.js'

class App extends Component {

  render() {
    return (
      <div className="App">
        {this.props.notificationExistance ? (<p>{this.props.notificationMessage}</p>) : ''}
        <button onClick={this.props.onNotificationRequest}>Click!</button>
      </div>
    );
  }
}

const mapStateToProps = (state) => {
  return {
    notificationExistance : state.length > 0,
    notificationMessage : state
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onNotificationRequest: () => dispatch(newNotification(new Date().toDateString()))
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(App)

The key code to solve this problem is as easy as pie as you can see, the only thing that appears different from the other answers is the function rootEpic.

Point 1. As with sagas, you have to combine the epics in order to get a top level function that receives a stream of actions and returns a stream of actions, so you can use it with the middleware factory createEpicMiddleware. In our case we only need one so we only have our rootEpic so we don't have to combine anything but it's a good to know fact.

Point 2. Our rootEpic which takes care about the side effects logic only takes about 5 lines of code which is awesome! Including the fact that is pretty much declarative!

Point 3. Line by line rootEpic explanation (in comments)

const rootEpic = (action$) => {
  // sets the incoming constant as a stream 
  // of actions with  type NEW_NOTIFICATION
  const incoming = action$.ofType(NEW_NOTIFICATION)
  // Merges the "incoming" stream with the stream resulting for each call
  // This functionality is similar to flatMap (or Promise.all in some way)
  // It creates a new stream with the values of incoming and 
  // the resulting values of the stream generated by the function passed
  // but it stops the merge when incoming gets a new value SO!,
  // in result: no quitNotification action is set in the resulting stream
  // in case there is a new alert
  const outgoing = incoming.switchMap((action) => {
    // creates of observable with the value passed 
    // (a stream with only one node)
    return Observable.of(quitNotification())
      // it waits before sending the nodes 
      // from the Observable.of(...) statement
      .delay(NOTIFICATION_TIMEOUT)
  });
  // we return the resulting stream
  return outgoing;
}

I hope it helps!


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to redux

TypeError [ERR_INVALID_ARG_TYPE]: The "path" argument must be of type string. Received type undefined raised when starting react app Uncaught Invariant Violation: Too many re-renders. React limits the number of renders to prevent an infinite loop Receiving "Attempted import error:" in react app Local package.json exists, but node_modules missing What is the best way to redirect a page using React Router? How do I fix "Expected to return a value at the end of arrow function" warning? ReactJS lifecycle method inside a function Component How to overcome the CORS issue in ReactJS Attach Authorization header for all axios requests React - Display loading screen while DOM is rendering?

Examples related to react-redux

Local package.json exists, but node_modules missing What is {this.props.children} and when you should use it? axios post request to send form data React-Redux: Actions must be plain objects. Use custom middleware for async actions How do I test axios in Jest? How do I fix "Expected to return a value at the end of arrow function" warning? React-router v4 this.props.history.push(...) not working How to use onClick event on react Link component? How do I add an element to array in reducer of React native redux? How to enable file upload on React's Material UI simple input?

Examples related to timeout

Waiting for Target Device to Come Online Spring Boot Java Config Set Session Timeout How to dispatch a Redux action with a timeout? Spring Boot REST API - request timeout? 5.7.57 SMTP - Client was not authenticated to send anonymous mail during MAIL FROM error How to set timeout in Retrofit library? How to set connection timeout with OkHttp How to modify the nodejs request default timeout time? How to handle ETIMEDOUT error? Timeout for python requests.get entire response