Redux saga receives function not object as action - javascript

I'm in the process of converting some of my code to use redux-saga (I previously had just redux-thunk but now run them both side-by-side) and have used this example as a basis.
The only problem I've run up against is using the following function as an argument to takeEvery sometimes works as expected, but other times breaks because it receives a function rather than an action object.
const requestAction = action => action.type.includes('REQUEST');
function* watchAuthenticationStatus() {
yield takeEvery(requestAction, ensureAuthenticated);
}
Logging the action out from inside requestAction shows me that a bunch of the time my actions come through as objects as expected e.g.
{ type: "data/FETCH_REQUEST", data: Array(1) }
However, I also see a lot of this type of thing:
ƒ (_x) {
return _ref.apply(this, arguments);
}
ƒ (_x2, _x3) {
return _ref3.apply(this, arguments);
}
I got around this for the time being by doing a check to see if action.type exists, but is anyone able to explain why I see these anonymous functions at times?
EDIT
As requested below, I checked the ordering of my middleware, I originally had:
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeEnhancers(applyMiddleware(sagaMiddleware, thunk))
);
sagaMiddleware.run(rootSaga);
Re-ordering saga and thunk to the following fixed my problem:
composeEnhancers(applyMiddleware(thunk, sagaMiddleware))

Re-ordering the middleware fixed this, originally it looked like this:
composeEnhancers(applyMiddleware(sagaMiddleware, thunk))
Placing thunk first fixed the issue:
composeEnhancers(applyMiddleware(thunk, sagaMiddleware))

Related

Redux - Calling storeAPI.disapatch(action) inside middleware causes 'too much recursion'

I'm working through the Redux fundamentals tutorial. In the section on 'Writing Custom Middleware', we learn that middleware are written as a series of three nested functions like so:
// Outer function:
function exampleMiddleware(storeAPI) {
return function wrapDispatch(next) {
return function handleAction(action) {
// Do anything here: pass the action onwards with next(action),
// or restart the pipeline with storeAPI.dispatch(action)
// Can also use storeAPI.getState() here
return next(action)
}
}
}
exampleMiddleware is explained as follows:
exampleMiddleware: The outer function is actually the "middleware"
itself. It will be called by applyMiddleware, and receives a storeAPI
object containing the store's {dispatch, getState} functions. These
are the same dispatch and getState functions that are actually part of
the store. If you call this dispatch function, it will send the action
to the start of the middleware pipeline. This is only called once.
I didn't understand what was meant by the second last sentence (If you call this dispatch function, it will send the action to the start of the middleware pipeline), so I tried calling store.dispatch(action) inside one of the middlewares provided in src/exampleAddons/middleware.js of the example app to see what happens and got "too much recursion". Here's the demo.
So storeAPI.dispatch() is the composed dispatch function of all the middlewares combined rather than the original store's dispatch, which would explain the recursion. But then what is the use of storeAPI.dispatch()? Am I using it incorrectly?
In applyMiddleware's source:
function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// ...1) createStore is called and the resulting store is saved as `store`
const store = createStore(...args)
// ...2) a `dispatch` variable is defined and assigned some function
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
// ...3) a middlewareAPI object is defined containing the store's getState method and the `dispatch` function from 2).
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// ...4) the middlewares passed to applyMiddleware are called with the `middlewareAPI` object from 3) and the resulting functions are saved in array `chain`.
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// ...5) the middlewares are composed and the resulting "composed" middleware function is called with `store.dispatch`.
// This returns a composed dispatch function that chains together the `handleAction` functions of all the middlewares passed to applyMiddleware.
// This composed dispatch gets assigned to the `dispatch` variable from 2).
// Since the `storeAPI.dispatch` is referencing this variable, calling `storeAPI.dispatch` now calls the composed middleware function, which causes the infinite loop.
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
The infinite loop seems to be the result of the re-assignment at step 5 in the annotations above. But I'm not sure if my reasoning is correct or if I'm even using storeAPI.dispatch correctly. I would appreciate any guidance the community could provide here as I wasn't able to find any examples of middleware that call storeAPI.dispatch().
Yes, calling storeAPI.dispatch() in a middleware sends the action to the very start of the middleware pipeline. That means that if we have middlewares a->b->c->store, and b calls storeAPI.dispatch({type: "some/action"}), middleware b will see that exact same action object go through almost immediately.
Because of that, a middleware should never call storeAPI.dispatch() unconditionally, because that will cause infinite loops! This is basically the same problem as something like calling setState() unconditionally in a React component useEffect hook. The effect runs after rendering, and setState() queues up another render, so if you always set state every time, you always force a re-render, and that's an infinite loop. Same thing here.
So, any use of storeAPI.dispatch() in a middleware should be wrapped in a conditional check so that it only happens some of the time, not all of the time.
But then what is the use of storeAPI.dispatch()?
Let's examine the naive implementation of thunk middleware. The middleware basically allows dispatch to receive a function (called dispatch function) as its argument (which is normally a plain action object).
const asyncFunctionMiddleware = store => next => action => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState); // (*)
}
return next(action);
}
Async logic will go inside the dispatch function which has been provided with two essential arguments: dispatch and getState - See line (*).
These two arguments are sufficient for developers to interact with the redux store in their async logic. And for the intended use-case, developer's will is to be able to dispatch an action as a normal operation (with all the provided middleware feature). Therefore, a store.dispatch is used here in the implementation.
What happen if we passed next instead?
Depend on the position of the thunk middleware in the middleware list, the next will be the next wrapped dispatch object followed by thunk middleware.
Imagine the order is a->thunk->b->store. Then dispatch using next in thunk will give your no effect of middleware a on the action.
This behavior isn't desired in the thunk use-case, hence using store.dispatch here is appropriate.
Therefore, thunk is a situation that we should make use of store.dispatch in middleware

Redux saga channel blocking inconsistently

I have a redux saga setup where for some reason my channel blocks when I'm trying to take from it and I can't work out why.
I have a PubSub mechanism which subscribes to an event and when received calls this function:
const sendRoundEnd = (msg, data) => {
console.log('putting round end')
roundEndChannel.put({
type: RUN_ENDED,
data: data.payload
})
}
I have a watcher for this channel defined like this:
function* watchRoundEndChannel() {
while(true) {
console.log('before take')
const action = yield take(roundEndChannel)
console.log('after take')
yield put(action)
}
}
And I have a reducer setup which is listening for the put of RUN_ENDED like this:
case RUN_ENDED:
console.log(action)
return {
...state,
isRunning: false,
roundResult: action.data
}
Finally, I have a roundEndChannel const within the file (but not within the functions) and I export the following function as part of an array which is fed into yield all[]:
takeEvery(roundEndChannel, watchRoundEndChannel)
So if my understanding is right, when I get the msg from my pubsub I should first hit sendRoundEnd which puts to roundEndChannel which should in turn put the RUN_ENDED action.
What's weird however is that when I run these functions and receive the message from the pubsub, the following is logged:
putting round end
before take
I never get to the after take which suggests to me that the channel doesn't have anything in it, but I'm pretty sure that isn't the case as it should have been put to in the event handler of the pubsub immediately prior.
It feels like I'm missing something simple here, does anyone have any ideas (or ways I can examine the channel at different points to see what's in there?)
Arg, managed to fix this. The problem was I had exported the watchRoundEndChannel wrapped in a takeEvery which was snatching my pushed events up.
I exported the function like this fork(watchRoundEndChannel) and things work as I expected.

Is it a bad idea to call a function mapped to a property and use that function's output immediately when using react-redux?

I have a question about the following example code, which seems to have something to do with redux data flow inside.
// action.js
export function doSomething() {
return {
type : 'FOO',
data : 'BAR'
};
}
// reducer.js
...
case types.FOO :
return update(state, { mystatus : { $set : action.data } });
...
// container.js
class MyTestClass {
...
handleButtonClick() {
this.props.doSomething(); // doSomething is just simple, synchronous action creator. In this function, state.foo
console.log(this.props.status); // expected output should be 'BAR' but, actual output is 'undefined'
}
...
}
const mapStateToProps = (state => {
return {
status : state.mystatus
};
};
const mapDispatchToProps = (dispatch) => {
return {
doSomething : () => {
return dispatch(doSomthing());
}
};
}
export default connect(mapStateToProps, mapDispatchToProps)(MyTestClass);
Now, this is my question.
If you see the code 'this.props.doSomething(); console.log(this.props.status);' in handleButtonClick method, the output of this code is supposed to be 'BAR'. However, I got 'undefined'.
this.props.doSomething();
console.log(this.props.status); // output is 'undefined'
And, If I change that part like following, I could get what I expected.
this.props.doSomething();
setTimeout(() => { console.log(this.props.status); }, 0); // now output is 'BAR'!!
I thought that "calling this.props.doSomething() => creating action => dispatching => mapping the changed state to the property by redux" was a series of synchronous control flow, but it does not seem to be.
There seems to be an asynchronous control flow in the process of being dispatched.
Is it a bad idea to call a function mapped to a property and use that function's output immediately when using react-redux? Could you explain how react-redux handle this inside?
Thnak you.
According to this, React Redux uses setState under the hood.
If you use bindings like React Redux, this is the point at which component.setState(newState) is called.
According to this, setState() is asynchronous and batched for performance gains.
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Also,
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
So if you want to react to state changing in terms of view, you should use renderhook for it. If you want to react to dispatching some action in terms of actions (logging, initiating another action), you should create middleware (generic cases) or use redux-saga/redux-thunk (receiving actual particular event and do something) depending on your task type.

Redux-auto: action chaining vs index reducer

Can anyone tell me what the difference between using chain on a reducer function and doing work in the main index reducer function in redux-auto
I want to save an error,
A) store/chat/send.js
import actions from 'redux-auto'
//...
function rejected(chat, payload, error){
return chat;
} onError.chain = (c, p, error) => actions.logger.save(error)
//...
or
B) store/logger/index.js
import actions from 'redux-auto'
import save from './save'
export default function (errorsLog = [], action)
{
if(action.type == actions.chat.send.rejected){
return save(errorsLog,action.payload)
}
return errorsLog
}
They both work
My questions:
I don't know what would be better. What is the difference?
Why would I use one over the other?
Also, can't I just call the action logger.save(...) inside the
rejected. Why does this chain feature exist?
Thanks for any help :)
A) Using the chain(OnError) will fire the action AFTER the source(rejected) reducer has completed. creating a new call across you store.
B) You are changing the state in the source reducer call
Your qustions:
1,2) Using chaining will make you code more readable as the next function is collocated with the source reducer, but but having it in the index group all action that will happen to that part of the store.
3) Calling an action function directly within a reducer function. Is an anti-pattern. This is dispatching an action in the middle of a dispatched action. The reducer will be operating on inconsistent data.
One of the main Redux point is predictability. We should use as more pure functions as possible. The reducer must not have any side-effects at all.
Recently I've worked on the same feature - error (user action, etc) logging. I think all of this actions are side-effects. They have no profit for user and can't be a part of main business logic.
That's why I use custom middleware to capture all actions I need to log. The action which I need to log I've marked with some meta-prop (ex. {log: 'errorLog'}) and in the middleware I checked every action. If it has a log prop then I make some logger magic.
Finally I've got clearly understandable code where all of logging side-effects incapsulated in middleware.

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.
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.
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 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:
// actions.js
// ...
let nextNotificationId = 0
export function showNotificationWithTimeout(dispatch, text) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
// component.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
// otherComponent.js
showNotificationWithTimeout(this.props.dispatch, 'You just logged out.')
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 accept dispatch as a prop so it can pass it further. You can’t just bind action creators with connect() anymore because showNotificationWithTimeout() 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 like showNotificationWithTimeout(). 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:
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
const store = createStore(
reducer,
applyMiddleware(thunk)
)
// It still recognizes plain object actions
store.dispatch({ type: 'INCREMENT' })
// But with thunk middleware, it also recognizes functions
store.dispatch(function (dispatch) {
// ... which themselves may dispatch many times
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
dispatch({ type: 'INCREMENT' })
setTimeout(() => {
// ... even asynchronously!
dispatch({ type: 'DECREMENT' })
}, 1000)
})
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:
// 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(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
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 accepts dispatch as the first argument.
How would we use it in our component? Definitely, we could write this:
// component.js
showNotificationWithTimeout('You just logged in.')(this.props.dispatch)
We are calling the async action creator to get the inner function that wants just dispatch, and then we pass dispatch.
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:
// component.js
this.props.dispatch(showNotificationWithTimeout('You just logged in.'))
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():
// 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(text) {
return function (dispatch) {
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
// component.js
import { connect } from 'react-redux'
// ...
this.props.showNotificationWithTimeout('You just logged in.')
// ...
export default connect(
mapStateToProps,
{ showNotificationWithTimeout }
)(MyComponent)
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:
// component.js
if (this.props.areNotificationsEnabled) {
showNotificationWithTimeout(this.props.dispatch, 'You just logged in.')
}
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 passes getState 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.
let nextNotificationId = 0
export function showNotificationWithTimeout(text) {
return function (dispatch, getState) {
// Unlike in a regular action creator, we can exit early in a thunk
// Redux doesn’t care about its return value (or lack of it)
if (!getState().areNotificationsEnabled) {
return
}
const id = nextNotificationId++
dispatch(showNotification(id, text))
setTimeout(() => {
dispatch(hideNotification(id))
}, 5000)
}
}
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 calling dispatch(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
store.dispatch({ type: 'SHOW_NOTIFICATION', text: 'You logged in.' })
setTimeout(() => {
store.dispatch({ type: 'HIDE_NOTIFICATION' })
}, 5000)
Don’t sweat it unless you know why you’re doing this.
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 a 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
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)
Honestly, 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.
A repository with sample projects
Current there are four sample projects:
Writing Async Code Inline
Extracting Async Action Creator
Use Redux Thunk
Use Redux Saga
The accepted answer is awesome.
But there is something missing:
No runnable sample projects, just some code snippets.
No sample code for other alternatives, such as:
Redux Saga
So I created the Hello Async repository to add the missing things:
Runnable projects. You can download and run them without modification.
Provide sample code for more alternatives:
Redux Saga
Redux Loop
...
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.
You can do this with redux-thunk. There is a guide in redux document for async actions like setTimeout.
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.
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:
const notificationHide = createLogic({
// the action type that will trigger this logic
type: 'NOTIFICATION_DISPLAY',
// your business logic can be applied in several
// execution hooks: validate, transform, process
// We are defining our code in the process hook below
// so it runs after the action hit reducers, hide 5s later
process({ getState, action }, dispatch) {
setTimeout(() => {
dispatch({ type: 'NOTIFICATION_CLEAR' });
}, 5000);
}
});
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.
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!
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.
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
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);
};
}
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.
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);
}
Redux actions can just return a plain object, not functions, callbacks, or asynchronous processes. For dispatching them through web API such as timeout() method you have to use redux-thunk middleware. It has been created for handling such a process.
First config redux-thunk through documentation redux-thunk
Second change your action creator this way:
const yourAction = millisecond => dispatch => {
setTimeout(() => {
dispatch({
type: 'YOUR_ACTIION_TYPE',
payload: yourWhatEverPayload
})
}, millisecond)
}
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} />;
}

Categories