I have an action that updates 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 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.
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:
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.
A repository with sample projects
Current there are four sample projects:
The accepted answer is awesome.
But there is something missing:
So I created the Hello Async repository to add the missing things:
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 are simple and pure.
Nothing is special with component.
Sagas are based on ES6 Generators
Compared to Redux Thunk
Pros
Cons
Please refer to the runnable project if the code snippets above don't answer all of your questions.
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?
An Epic is the core primitive of redux-observable.
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
index.js
App.js
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)
I hope it helps!
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
Result
Screenshot of my production app Stample.co
Code
Here I named the notification a
toast
but this is a naming detail.And the reducer:
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 theTOAST_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:
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.
Why should it be so hard? It's just UI logic. Use a dedicated action to set notification data:
and a dedicated component to display it:
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 componentThe 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.
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
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.
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
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:
Or, if you have previously bound them with
connect()
: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:
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:
Now components can use
showNotificationWithTimeout
without duplicating this logic or having race conditions with different notifications:Why does
showNotificationWithTimeout()
acceptdispatch
as the first argument? Because it needs to dispatch actions to the store. Normally a component has access todispatch
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: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 real store exported from a specific module. You can’t even reset its state from outside.
So while you technically can export a singleton store from a module, we discourage it. Don’t do this unless you are sure that your app will never add server rendering.
Getting back to the previous version:
This solves the problems with duplication of logic and saves us from race conditions.
Thunk Middleware
For simple apps, the approach should suffice. Don’t worry about middleware if you’re happy with it.
In larger apps, however, you might find certain inconveniences around it.
For example, it seems unfortunate that we have to pass
dispatch
around. This makes it trickier to separate container and presentational components because any component that dispatches Redux actions asynchronously in the manner above has to acceptdispatch
as a prop so it can pass it further. You can’t just bind action creators withconnect()
anymore becauseshowNotificationWithTimeout()
is not really an action creator. It does not return a Redux action.In addition, it can be awkward to remember which functions are synchronous action creators like
showNotification()
and which are asynchronous helpers likeshowNotificationWithTimeout()
. You have to use them differently and be careful not to mistake them with each other.This was the motivation for finding a way to “legitimize” this pattern of providing
dispatch
to a helper function, and help Redux “see” such asynchronous action creators as a special case of normal action creators rather than totally different functions.If you’re still with us and you also recognize as a problem in your app, you are welcome to use the Redux Thunk middleware.
In a gist, Redux Thunk teaches Redux to recognize special kinds of actions that are in fact functions:
When this middleware is enabled, if you dispatch a function, Redux Thunk middleware will give it
dispatch
as an argument. It will also “swallow” such actions so don’t worry about your reducers receiving weird function arguments. Your reducers will only receive plain object actions—either emitted directly, or emitted by the functions as we just described.This does not look very useful, does it? Not in this particular situation. However it lets us declare
showNotificationWithTimeout()
as a regular Redux action creator:Note how the function is almost identical to the one we wrote in the previous section. However it doesn’t accept
dispatch
as the first argument. Instead it returns a function that acceptsdispatch
as the first argument.How would we use it in our component? Definitely, we could write this:
We are calling the async action creator to get the inner function that wants just
dispatch
, and then we passdispatch
.However this is even more awkward than the original version! Why did we even go that way?
Because of what I told you before. If Redux Thunk middleware is enabled, any time you attempt to dispatch a function instead of an action object, the middleware will call that function with
dispatch
method itself as the first argument.So we can do this instead:
Finally, dispatching an asynchronous action (really, a series of actions) looks no different than dispatching a single action synchronously to the component. Which is good because components shouldn’t care whether something happens synchronously or asynchronously. We just abstracted that away.
Notice that since we “taught” Redux to recognize such “special” action creators (we call them thunk action creators), we can now use them in any place where we would use regular action creators. For example, we can use them with
connect()
:Reading State in Thunks
Usually your reducers contain the business logic for determining the next state. However, reducers only kick in after the actions are dispatched. What if you have a side effect (such as calling an API) in a thunk action creator, and you want to prevent it under some condition?
Without using the thunk middleware, you’d just do this check inside the component:
However, the point of extracting an action creator was to centralize this repetitive logic across many components. Fortunately, Redux Thunk offers you a way to read the current state of the Redux store. In addition to
dispatch
, it also passesgetState
as the second argument to the function you return from your thunk action creator. This lets the thunk read the current state of the store.Don’t abuse this pattern. It is good for bailing out of API calls when there is cached data available, but it is not a very good foundation to build your business logic upon. If you use
getState()
only to conditionally dispatch different actions, consider putting the business logic into the reducers instead.Next Steps
Now that you have a basic intuition about how thunks work, check out Redux async example which uses them.
You may find many examples in which thunks return Promises. This is not required but can be very convenient. Redux doesn’t care what you return from a thunk, but it gives you its return value from
dispatch()
. This is why you can return a Promise from a thunk and wait for it to complete by callingdispatch(someThunkReturningPromise()).then(...)
.You may also split complex thunk action creators into several smaller thunk action creators. The
dispatch
method provided by thunks can accept thunks itself, so you can apply the pattern recursively. Again, this works best with Promises because you can implement asynchronous control flow on top of that.For some apps, you may find yourself in a situation where your asynchronous control flow requirements are too complex to be expressed with thunks. For example, retrying failed requests, reauthorization flow with tokens, or a step-by-step onboarding can be too verbose and error-prone when written this way. In this case, you might want to look at more advanced asynchronous control flow solutions such as Redux Saga or Redux Loop. Evaluate them, compare the examples relevant to your needs, and pick the one you like the most.
Finally, don’t use anything (including thunks) if you don’t have the genuine need for them. Remember that, depending on the requirements, your solution might look as simple as
Don’t sweat it unless you know why you’re doing this.