snippetjavascriptCritical
How to dispatch a Redux action with a timeout?
Viewed 0 times
actionwithhowdispatchtimeoutredux
Problem
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
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.Solution
Don’t fall into the trap of thinking a library should prescribe how to do everything. If you want to do something with a timeout in JavaScript, you need to use
Redux does offer some alternative ways of dealing with asynchronous stuff, but you should only use those when you realize you are repeating too much code. Unless you have this problem, use what the language offers and go for the simplest solution.
Writing Async Code Inline
This is by far the simplest way. And there’s nothing specific to Redux here.
Similarly, from inside a connected component:
The only difference is that in a connected component you usually don’t have access to the store itself, but get either
If you don’t like making typos when dispatching the same actions from different components, you might want to extract action creators instead of dispatching action objects inline:
Or, if you have previously bound them with
So far we have not used any middleware or other advanced concept.
Extracting Async Action Creator
The approach above works fine in simple cases but you might find that it has a few problems:
To solve these problems, you would need to extract a function that centralizes the timeout logic and dispatches those two actions. It might look like this:
Now components can use
Why does
If you had a singleton store exported from some module, you could just import it and
This looks simpler but we don’t recommend this approach. The main reason we dislike it is because it forces store to be a singleton. This makes it very hard to implement server rendering. On the server, you will want each request to have its own store, so that different users get different preloaded data.
A singleton store also makes testing harder. You can no longer mock a store when testing action creators because they reference a specific
setTimeout. There is no reason why Redux actions should be any different.Redux does offer some alternative ways of dealing with asynchronous stuff, but you should only use those when you realize you are repeating too much code. Unless you have this problem, use what the language offers and go for the simplest solution.
Writing Async Code Inline
This is by far the simplest way. And there’s nothing specific to Redux here.
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)Similarly, from inside a connected component:
this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)The only difference is that in a connected component you usually don’t have access to the store itself, but get either
dispatch() or specific action creators injected as props. However this doesn’t make any difference for us.If you don’t like making typos when dispatching the same actions from different components, you might want to extract action creators instead of dispatching action objects inline:
// actions.js
export function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
return { type: 'HIDE_NOTIFICATION' }
}
// component.js
import { showNotification, hideNotification } from '../actions'
this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
this.props.dispatch(hideNotification())
}, 5000)Or, if you have previously bound them with
connect():this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)So far we have not used any middleware or other advanced concept.
Extracting Async Action Creator
The approach above works fine in simple cases but you might find that it has a few problems:
- It forces you to duplicate this logic anywhere you want to show a notification.
- The notifications have no IDs so you’ll have a race condition if you show two notifications fast enough. When the first timeout finishes, it will dispatch
HIDE_NOTIFICATION, erroneously hiding the second notification sooner than after the timeout.
To solve these problems, you would need to extract a function that centralizes the timeout logic and dispatches those two actions. It might look like this:
// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
// Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
// for the notification that is not currently visible.
// Alternatively, we could store the timeout ID and call
// clearTimeout(), but we’d still want to do it in a single place.
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}Now components can use
showNotificationWithTimeout without duplicating this logic or having race conditions with different notifications:// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')Why does
showNotificationWithTimeout() accept dispatch as the first argument? Because it needs to dispatch actions to the store. Normally a component has access to dispatch but since we want an external function to take control over dispatching, we need to give it control over dispatching.If you had a singleton store exported from some module, you could just import it and
dispatch directly on it instead:// store.js
export default createStore(reducer)
// actions.js
import store from './store'
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
const id = nextNotificationId++
store.dispatch(showNotification(id, text))
setTimeout(() => {
store.dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout('You just logged in.')
// otherComponent.js
showNotificationWithTimeout('You just logged out.')This looks simpler but we don’t recommend this approach. The main reason we dislike it is because it forces store to be a singleton. This makes it very hard to implement server rendering. On the server, you will want each request to have its own store, so that different users get different preloaded data.
A singleton store also makes testing harder. You can no longer mock a store when testing action creators because they reference a specific
Code Snippets
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)this.props.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
this.props.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)// actions.js
export function showNotification(text) {
return { type: 'SHOW_NOTIFICATION', text }
}
export function hideNotification() {
return { type: 'HIDE_NOTIFICATION' }
}
// component.js
import { showNotification, hideNotification } from '../actions'
this.props.dispatch(showNotification('You just logged in.'))
setTimeout(() => {
this.props.dispatch(hideNotification())
}, 5000)this.props.showNotification('You just logged in.')
setTimeout(() => {
this.props.hideNotification()
}, 5000)// actions.js
function showNotification(id, text) {
return { type: 'SHOW_NOTIFICATION', id, text }
}
function hideNotification(id) {
return { type: 'HIDE_NOTIFICATION', id }
}
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
// Assigning IDs to notifications lets reducer ignore HIDE_NOTIFICATION
// for the notification that is not currently visible.
// Alternatively, we could store the timeout ID and call
// clearTimeout(), but we’d still want to do it in a single place.
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}Context
Stack Overflow Q#35411423, score: 3017
Revisions (0)
No revisions yet.