Related
Suppose I have two dispatches I want to fire in order called
dispatch(action1())
dispatch(action2())
Action1 is an action creator created using
createAsyncThunk
method from redux/toolkit.
Therefore, it uses async... await in the process.
Action2 is a synchronous process.
I want to make
`dispatch(action2())` run only after `action1()` has been dispatched.
How can I achieve this?
I have tried doing
dispatch(action1(), dispatch(action2())
but I have found that dispatching inside of a reducer is an anti-pattern since reducers are pure.
See Unwrapping Result Actions
Thunks may return a value when dispatched. A common use case is to return a promise from the thunk, dispatch the thunk from a component, and then wait for the promise to resolve before doing additional work:
So you can do this:
dispatch(action1()).then(() => {
dispatch(action2());
})
A simple way might be to let action1 conditionally dispatch action2 where you need it:
const action1 = createAsyncThunk(
'actions/1',
async (args, thunkAPI) => {
const response = await someApi.fetchStuff(args.id);
if (args.condition) {
thunkAPI.dispatch(action2());
}
return response.data
}
);
This only works if you don't depend on the reducer having updated the state at that point (usually with this type of scenario it's about the asynchronous work having finished).
If you however also depend on the state update, try the dispatch(action1()).then(...) answer someone else gave.
Another option is to write a "classic" thunk (not using createAsyncThunk) that coordinates the sequence of action1 and action2.
I personally prefer the last option since bigger thunks can be composed nicely of many smaller thunks.
In case you're wondering, createAsyncThunk is not meant to dispatch another action when returning its result - which would have also solved your problem: https://github.com/reduxjs/redux-toolkit/issues/773
I have defined two functions in my ActionCreator.js file
First:
export const getAudioForVerification = ()=>{
return fetch(baseUrl+'audio',{
// Get Request
}
.then(response=>response.json());}
Second:
export const audioVerificationResult = (audioId,verificationResult) =>(dispatch)=>{
return fetch(baseUrl+'audio',{
// PUT Request
})
.then(response=>response.json());
}
MainFunction:
const mapDispatchToProps = dispatch => ({
getAudioForVerification: ()=>dispatch(getAudioForVerification),
audioVerificationResult: (audioId,verificationResult)=>dispatch(audioVerificationResult(audioId,verificationResult))
});
Q1: If I remove dispatch from my Second function: audioVerificationResult I get an error
Actions must be plain objects. Use custom middleware for async actions.
Why doesn't such an error appear for the first function as well?
Q2: The first function returns a promise (I can use .then in my MainComponent after I call this function) while the second one doesnot. Why?
I have started learning about Promises, Redux and Thunk (Web dev in general) quite recently. If the questions are too broad please direct me to a learning source.
Thank you for your time.
The functions in your ActionCreator.js are not exactly action creators since they are not returning plain object like the redux docs suggests. They are just js functions.
You don't need to dispatch if you are using .then() in your component and reading the results/data. dispatch only helps you with the redux store updates.
Hope this answer helps you to understand granular details about middleware better.
I am learning redux-thunk middleware as a beginner react developper, and I don't understand why dos the function (returned by redux-thunk) returns a promise (returned by fetch())
I tried not to return anything, and it worked, so why do we return it?
export function getCourses() {
return fetch(baseUrl)
.then(handleResponse)
.catch(handleError);
}
export function loadCourses() {
return function(dispatch) {
dispatch(beginApiCall());// dispatch some synchronous action
return courseApi
.getCourses().then(courses => {
dispatch(loadCourseSuccess(courses));
}).catch(error => {throw error;});
};
}
For the component named MyComponent dispatching loadCourses() action
function MyComponent(props){
.......
useEffect(() => {
loadCourses()
});
const mapDispatchToProps = {
loadCourses,
.....
}
}
I think i got the answer(from a colleague).
if you want to chain certain activities, your action would need to return a Promise.
it's just a good way to allow chaining activities after the result is returned!
Well, first of all the function returns some data because you asked it to return some sort of result return function(dispatch) {...}.
If you want to ignore the returned result just remove the return from return function(dispatch) {...}.
Secondly, the function returns a promise because of the way that you have written your call to API functions (wrapped inside promise and not returning callbacks upon function completion).
If you want to get the actual result of the API call you should use the Async / Await syntax.
With a plain basic Redux store, you can only do simple synchronous updates by dispatching an action. Middleware extends the store's abilities and let you write async logic that interacts with the store.
Thunks are the recommended middleware for basic Redux side effects logic, including complex synchronous logic that needs access to the store, and simple async logic like AJAX requests.https://github.com/gaearon/redux-thunk
The thunk middleware knows how to turn thunk async actions into actions, so you just have to have your simple_action() to be a thunk and the thunk middleware will do the job for you, if the middleware see a normal action, he will dispatch this action as normal action but if it's an async function it will turn your async action into normal action.
You can also see return promise from store after redux thunk dispatch
According to the docs, "Without middleware, Redux store only supports synchronous data flow". I don't understand why this is the case. Why can't the container component call the async API, and then dispatch the actions?
For example, imagine a simple UI: a field and a button. When user pushes the button, the field gets populated with data from a remote server.
import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';
const ActionTypes = {
STARTED_UPDATING: 'STARTED_UPDATING',
UPDATED: 'UPDATED'
};
class AsyncApi {
static getFieldValue() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 100));
}, 1000);
});
return promise;
}
}
class App extends React.Component {
render() {
return (
<div>
<input value={this.props.field}/>
<button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
{this.props.isWaiting && <div>Waiting...</div>}
</div>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
field: React.PropTypes.any,
isWaiting: React.PropTypes.bool
};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
switch (action.type) {
case ActionTypes.STARTED_UPDATING:
return { ...state, isWaiting: true };
case ActionTypes.UPDATED:
return { ...state, isWaiting: false, field: action.payload };
default:
return state;
}
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
(state) => {
return { ...state };
},
(dispatch) => {
return {
update: () => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
};
})(App);
export default class extends React.Component {
render() {
return <Provider store={store}><ConnectedApp/></Provider>;
}
}
When the exported component is rendered, I can click the button and the input is updated correctly.
Note the update function in the connect call. It dispatches an action that tells the App that it is updating, and then performs an async call. After the call finishes, the provided value is dispatched as a payload of another action.
What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?
EDIT: I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past. For example, here's a user trying to provide a better explanation for async data flow:
The action creator itself is still a pure function, but the thunk function it returns doesn't need to be, and it can do our async calls
Action creators are no longer required to be pure. So, thunk/promise middleware was definitely required in the past, but it seems that this is no longer the case?
What is wrong with this approach? Why would I want to use Redux Thunk or Redux Promise, as the documentation suggests?
There is nothing wrong with this approach. It’s just inconvenient in a large application because you’ll have different components performing the same actions, you might want to debounce some actions, or keep some local state like auto-incrementing IDs close to action creators, etc. So it is just easier from the maintenance point of view to extract action creators into separate functions.
You can read my answer to “How to dispatch a Redux action with a timeout” for a more detailed walkthrough.
Middleware like Redux Thunk or Redux Promise just gives you “syntax sugar” for dispatching thunks or promises, but you don’t have to use it.
So, without any middleware, your action creator might look like
// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
return fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
// component
componentWillMount() {
loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}
But with Thunk Middleware you can write it like this:
// action creator
function loadData(userId) {
return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
);
}
// component
componentWillMount() {
this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}
So there is no huge difference. One thing I like about the latter approach is that the component doesn’t care that the action creator is async. It just calls dispatch normally, it can also use mapDispatchToProps to bind such action creator with a short syntax, etc. The components don’t know how action creators are implemented, and you can switch between different async approaches (Redux Thunk, Redux Promise, Redux Saga) without changing the components. On the other hand, with the former, explicit approach, your components know exactly that a specific call is async, and needs dispatch to be passed by some convention (for example, as a sync parameter).
Also think about how this code will change. Say we want to have a second data loading function, and to combine them in a single action creator.
With the first approach we need to be mindful of what kind of action creator we are calling:
// action creators
function loadSomeData(dispatch, userId) {
return fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
function loadOtherData(dispatch, userId) {
return fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
);
}
function loadAllData(dispatch, userId) {
return Promise.all(
loadSomeData(dispatch, userId), // pass dispatch first: it's async
loadOtherData(dispatch, userId) // pass dispatch first: it's async
);
}
// component
componentWillMount() {
loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}
With Redux Thunk action creators can dispatch the result of other action creators and not even think whether those are synchronous or asynchronous:
// action creators
function loadSomeData(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
function loadOtherData(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
);
}
function loadAllData(userId) {
return dispatch => Promise.all(
dispatch(loadSomeData(userId)), // just dispatch normally!
dispatch(loadOtherData(userId)) // just dispatch normally!
);
}
// component
componentWillMount() {
this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}
With this approach, if you later want your action creators to look into current Redux state, you can just use the second getState argument passed to the thunks without modifying the calling code at all:
function loadSomeData(userId) {
// Thanks to Redux Thunk I can use getState() here without changing callers
return (dispatch, getState) => {
if (getState().data[userId].isLoaded) {
return Promise.resolve();
}
fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
);
}
}
If you need to change it to be synchronous, you can also do this without changing any calling code:
// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
return {
type: 'LOAD_SOME_DATA_SUCCESS',
data: localStorage.getItem('my-data')
}
}
So the benefit of using middleware like Redux Thunk or Redux Promise is that components aren’t aware of how action creators are implemented, and whether they care about Redux state, whether they are synchronous or asynchronous, and whether or not they call other action creators. The downside is a little bit of indirection, but we believe it’s worth it in real applications.
Finally, Redux Thunk and friends is just one possible approach to asynchronous requests in Redux apps. Another interesting approach is Redux Saga which lets you define long-running daemons (“sagas”) that take actions as they come, and transform or perform requests before outputting actions. This moves the logic from action creators into sagas. You might want to check it out, and later pick what suits you the most.
I searched the Redux repo for clues, and found that Action Creators were required to be pure functions in the past.
This is incorrect. The docs said this, but the docs were wrong.
Action creators were never required to be pure functions.
We fixed the docs to reflect that.
You don't.
But... you should use redux-saga :)
Dan Abramov's answer is right about redux-thunk but I will talk a bit more about redux-saga that is quite similar but more powerful.
Imperative VS declarative
DOM: jQuery is imperative / React is declarative
Monads: IO is imperative / Free is declarative
Redux effects: redux-thunk is imperative / redux-saga is declarative
When you have a thunk in your hands, like an IO monad or a promise, you can't easily know what it will do once you execute. The only way to test a thunk is to execute it, and mock the dispatcher (or the whole outside world if it interacts with more stuff...).
If you are using mocks, then you are not doing functional programming.
Seen through the lens of side-effects, mocks are a flag that your code is impure, and in the functional programmer's eye, proof that something is wrong. Instead of downloading a library to help us check the iceberg is intact, we should be sailing around it.
A hardcore TDD/Java guy once asked me how you do mocking in Clojure. The answer is, we usually don't. We usually see it as a sign we need to refactor our code.
Source
The sagas (as they got implemented in redux-saga) are declarative and like the Free monad or React components, they are much easier to test without any mock.
See also this article:
in modern FP, we shouldn’t write programs — we should write descriptions of programs, which we can then introspect, transform, and interpret at will.
(Actually, Redux-saga is like a hybrid: the flow is imperative but the effects are declarative)
Confusion: actions/events/commands...
There is a lot of confusion in the frontend world on how some backend concepts like CQRS / EventSourcing and Flux / Redux may be related, mostly because in Flux we use the term "action" which can sometimes represent both imperative code (LOAD_USER) and events (USER_LOADED). I believe that like event-sourcing, you should only dispatch events.
Using sagas in practice
Imagine an app with a link to a user profile. The idiomatic way to handle this with each middleware would be:
redux-thunk
<div onClick={e => dispatch(actions.loadUserProfile(123)}>Robert</div>
function loadUserProfile(userId) {
return dispatch => fetch(`http://data.com/${userId}`)
.then(res => res.json())
.then(
data => dispatch({ type: 'USER_PROFILE_LOADED', data }),
err => dispatch({ type: 'USER_PROFILE_LOAD_FAILED', err })
);
}
redux-saga
<div onClick={e => dispatch({ type: 'USER_NAME_CLICKED', payload: 123 })}>Robert</div>
function* loadUserProfileOnNameClick() {
yield* takeLatest("USER_NAME_CLICKED", fetchUser);
}
function* fetchUser(action) {
try {
const userProfile = yield fetch(`http://data.com/${action.payload.userId }`)
yield put({ type: 'USER_PROFILE_LOADED', userProfile })
}
catch(err) {
yield put({ type: 'USER_PROFILE_LOAD_FAILED', err })
}
}
This saga translates to:
every time a username gets clicked, fetch the user profile and then dispatch an event with the loaded profile.
As you can see, there are some advantages of redux-saga.
The usage of takeLatest permits to express that you are only interested to get the data of the last username clicked (handle concurrency problems in case the user click very fast on a lot of usernames). This kind of stuff is hard with thunks. You could have used takeEvery if you don't want this behavior.
You keep action creators pure. Note it's still useful to keep actionCreators (in sagas put and components dispatch), as it might help you to add action validation (assertions/flow/typescript) in the future.
Your code becomes much more testable as the effects are declarative
You don't need anymore to trigger rpc-like calls like actions.loadUser(). Your UI just needs to dispatch what HAS HAPPENED. We only fire events (always in the past tense!) and not actions anymore. This means that you can create decoupled "ducks" or Bounded Contexts and that the saga can act as the coupling point between these modular components.
This means that your views are more easy to manage because they don't need anymore to contain that translation layer between what has happened and what should happen as an effect
For example imagine an infinite scroll view. CONTAINER_SCROLLED can lead to NEXT_PAGE_LOADED, but is it really the responsibility of the scrollable container to decide whether or not we should load another page? Then he has to be aware of more complicated stuff like whether or not the last page was loaded successfully or if there is already a page that tries to load, or if there is no more items left to load? I don't think so: for maximum reusability the scrollable container should just describe that it has been scrolled. The loading of a page is a "business effect" of that scroll
Some might argue that generators can inherently hide state outside of redux store with local variables, but if you start to orchestrate complex things inside thunks by starting timers etc you would have the same problem anyway. And there's a select effect that now permits to get some state from your Redux store.
Sagas can be time-traveled and also enables complex flow logging and dev-tools that are currently being worked on. Here is some simple async flow logging that is already implemented:
Decoupling
Sagas are not only replacing redux thunks. They come from backend / distributed systems / event-sourcing.
It is a very common misconception that sagas are just here to replace your redux thunks with better testability. Actually this is just an implementation detail of redux-saga. Using declarative effects is better than thunks for testability, but the saga pattern can be implemented on top of imperative or declarative code.
In the first place, the saga is a piece of software that permits to coordinate long running transactions (eventual consistency), and transactions across different bounded contexts (domain driven design jargon).
To simplify this for frontend world, imagine there is widget1 and widget2. When some button on widget1 is clicked, then it should have an effect on widget2. Instead of coupling the 2 widgets together (ie widget1 dispatch an action that targets widget2), widget1 only dispatch that its button was clicked. Then the saga listen for this button click and then update widget2 by dispaching a new event that widget2 is aware of.
This adds a level of indirection that is unnecessary for simple apps, but make it more easy to scale complex applications. You can now publish widget1 and widget2 to different npm repositories so that they never have to know about each others, without having them to share a global registry of actions. The 2 widgets are now bounded contexts that can live separately. They do not need each others to be consistent and can be reused in other apps as well. The saga is the coupling point between the two widgets that coordinate them in a meaningful way for your business.
Some nice articles on how to structure your Redux app, on which you can use Redux-saga for decoupling reasons:
http://jaysoo.ca/2016/02/28/organizing-redux-application/
http://marmelab.com/blog/2015/12/17/react-directory-structure.html
https://github.com/slorber/scalable-frontend-with-elm-or-redux
A concrete usecase: notification system
I want my components to be able to trigger the display of in-app notifications. But I don't want my components to be highly coupled to the notification system that has its own business rules (max 3 notifications displayed at the same time, notification queueing, 4 seconds display-time etc...).
I don't want my JSX components to decide when a notification will show/hide. I just give it the ability to request a notification, and leave the complex rules inside the saga. This kind of stuff is quite hard to implement with thunks or promises.
I've described here how this can be done with saga
Why is it called a Saga?
The term saga comes from the backend world. I initially introduced Yassine (the author of Redux-saga) to that term in a long discussion.
Initially, that term was introduced with a paper, the saga pattern was supposed to be used to handle eventual consistency in distributed transactions, but its usage has been extended to a broader definition by backend developers so that it now also covers the "process manager" pattern (somehow the original saga pattern is a specialized form of process manager).
Today, the term "saga" is confusing as it can describe 2 different things. As it is used in redux-saga, it does not describe a way to handle distributed transactions but rather a way to coordinate actions in your app. redux-saga could also have been called redux-process-manager.
See also:
Interview of Yassine about Redux-saga history
Kella Byte: Claryfing the Saga pattern
Microsoft CQRS Journey: A Saga on Sagas
Medium response of Yassine
Alternatives
If you don't like the idea of using generators but you are interested by the saga pattern and its decoupling properties, you can also achieve the same with redux-observable which uses the name epic to describe the exact same pattern, but with RxJS. If you're already familiar with Rx, you'll feel right at home.
const loadUserProfileOnNameClickEpic = action$ =>
action$.ofType('USER_NAME_CLICKED')
.switchMap(action =>
Observable.ajax(`http://data.com/${action.payload.userId}`)
.map(userProfile => ({
type: 'USER_PROFILE_LOADED',
userProfile
}))
.catch(err => Observable.of({
type: 'USER_PROFILE_LOAD_FAILED',
err
}))
);
Some redux-saga useful resources
Redux-saga vs Redux-thunk with async/await
Managing processes in Redux Saga
From actionsCreators to Sagas
Snake game implemented with Redux-saga
2017 advises
Don't overuse Redux-saga just for the sake of using it. Testable API calls only are not worth it.
Don't remove thunks from your project for most simple cases.
Don't hesitate to dispatch thunks in yield put(someActionThunk) if it makes sense.
If you are frightened of using Redux-saga (or Redux-observable) but just need the decoupling pattern, check redux-dispatch-subscribe: it permits to listen to dispatches and trigger new dispatches in listener.
const unsubscribe = store.addDispatchListener(action => {
if (action.type === 'ping') {
store.dispatch({ type: 'pong' });
}
});
The short answer: seems like a totally reasonable approach to the asynchrony problem to me. With a couple caveats.
I had a very similar line of thought when working on a new project we just started at my job. I was a big fan of vanilla Redux's elegant system for updating the store and rerendering components in a way that stays out of the guts of a React component tree. It seemed weird to me to hook into that elegant dispatch mechanism to handle asynchrony.
I ended up going with a really similar approach to what you have there in a library I factored out of our project, which we called react-redux-controller.
I ended up not going with the exact approach you have above for a couple reasons:
The way you have it written, those dispatching functions don't have access to the store. You can somewhat get around that by having your UI components pass in all of the info the dispatching function needs. But I'd argue that this couples those UI components to the dispatching logic unnecessarily. And more problematically, there's no obvious way for the dispatching function to access updated state in async continuations.
The dispatching functions have access to dispatch itself via lexical scope. This limits the options for refactoring once that connect statement gets out of hand -- and it's looking pretty unwieldy with just that one update method. So you need some system for letting you compose those dispatcher functions if you break them up into separate modules.
Take together, you have to rig up some system to allow dispatch and the store to be injected into your dispatching functions, along with the parameters of the event. I know of three reasonable approaches to this dependency injection:
redux-thunk does this in a functional way, by passing them into your thunks (making them not exactly thunks at all, by dome definitions). I haven't worked with the other dispatch middleware approaches, but I assume they're basically the same.
react-redux-controller does this with a coroutine. As a bonus, it also gives you access to the "selectors", which are the functions you may have passed in as the first argument to connect, rather than having to work directly with the raw, normalized store.
You could also do it the object-oriented way by injecting them into the this context, through a variety of possible mechanisms.
Update
It occurs to me that part of this conundrum is a limitation of react-redux. The first argument to connect gets a state snapshot, but not dispatch. The second argument gets dispatch but not the state. Neither argument gets a thunk that closes over the current state, for being able to see updated state at the time of a continuation/callback.
Abramov's goal - and everyone's ideally - is simply to encapsulate complexity (and async calls) in the place where it's most appropriate and reusable.
Where's the best place to do that in the standard Redux dataflow? How about:
Reducers? No way. They should be pure functions with no side-effects. Updating the store is serious, complicated business. Don't contaminate it.
Dumb View Components? Definitely No. They have one concern: presentation and user-interaction, and should be as simple as possible.
Container Components? Possible, but sub-optimal. It makes sense in that the container is a place where we encapsulate some view related complexity and interact with the store, but:
Containers do need to be more complex than dumb components, but it's still a single responsibility: providing bindings between view and state/store. Your async logic is a whole separate concern from that.
By placing it in a container, you'd be locking your async logic into a single context, coupled to one or more views/routes. Bad idea. Ideally it's all reusable, and totally decoupled from the views.
(Like all rules, there could be an exception if you have stateful binding logic that happens to be reusable across multiple contexts, or if you can somehow generalize all of your state into something like an integrated GraphQL schema. OK, fine, that could be cool. But... most of the time the bindings seem to end up pretty context/view specific.)
Some other Service Module? Bad idea: you'd need to inject access to the store, which is a maintainability/testability nightmare. Better to go with the grain of Redux and access the store only using the APIs/models provided.
Actions and the Middlewares that interpret them? Why not?! For starters, it's the only major option we have left. :-) More logically, the action system is decoupled execution logic that you can use from anywhere. It's got access to the store and can dispatch more actions. It has a single responsibility which is to organize the flow of control and data around the application, and most async fits right into that.
What about the Action Creators? Why not just do async in there, instead of in the actions themselves, and in Middleware?
First and most important, the creators don't have access to the store, as middleware does. That means you can't dispatch new contingent actions, can't read from the store to compose your async, etc.
So, keep complexity in a place that's complex of necessity, and keep everything else simple. The creators can then be simple, relatively pure functions that are easy to test.
To answer the question that is asked in the beginning:
Why can't the container component call the async API, and then dispatch the actions?
Keep in mind that those docs are for Redux, not Redux plus React. Redux stores hooked up to React components can do exactly what you say, but a Plain Jane Redux store with no middleware doesn't accept arguments to dispatch except plain ol' objects.
Without middleware you could of course still do
const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));
But it's a similar case where the asynchrony is wrapped around Redux rather than handled by Redux. So, middleware allows for asynchrony by modifying what can be passed directly to dispatch.
That said, the spirit of your suggestion is, I think, valid. There are certainly other ways you could handle asynchrony in a Redux + React application.
One benefit of using middleware is that you can continue to use action creators as normal without worrying about exactly how they're hooked up. For example, using redux-thunk, the code you wrote would look a lot like
function updateThing() {
return dispatch => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
}
const ConnectedApp = connect(
(state) => { ...state },
{ update: updateThing }
)(App);
which doesn't look all that different from the original — it's just shuffled a bit — and connect doesn't know that updateThing is (or needs to be) asynchronous.
If you also wanted to support promises, observables, sagas, or crazy custom and highly declarative action creators, then Redux can do it just by changing what you pass to dispatch (aka, what you return from action creators). No mucking with the React components (or connect calls) necessary.
OK, let's start to see how middleware working first, that quite answer the question, this is the source code applyMiddleWare function in Redux:
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
};
chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = compose.apply(undefined, chain)(store.dispatch);
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}
Look at this part, see how our dispatch become a function.
...
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
Note that each middleware will be given the dispatch and getState functions as named arguments.
OK, this is how Redux-thunk as one of the most used middlewares for Redux introduce itself:
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 as you see, it will return a function instead an action, means you can wait and call it anytime you want as it's a function...
So what the heck is thunk? That's how it's introduced in Wikipedia:
In computer programming, a thunk is a subroutine used to inject an
additional calculation into another subroutine. Thunks are primarily
used to delay a calculation until it is needed, or to insert
operations at the beginning or end of the other subroutine. They have
a variety of other applications to compiler code generation and in
modular programming.
The term originated as a jocular derivative of "think".
A thunk is a function that wraps an expression to delay its
evaluation.
//calculation of 1 + 2 is immediate
//x === 3
let x = 1 + 2;
//calculation of 1 + 2 is delayed
//foo can be called later to perform the calculation
//foo is a thunk!
let foo = () => 1 + 2;
So see how easy the concept is and how it can help you manage your async actions...
That's something you can live without it, but remember in programming there are always better, neater and proper ways to do things...
There are synchronous action creators and then there are asynchronous action creators.
A synchronous action creator is one that when we call it, it immediately returns an Action object with all the relevant data attached to that object and its ready to be processed by our reducers.
Asynchronous action creators is one in which it will require a little bit of time before it is ready to eventually dispatch an action.
By definition, anytime you have an action creator that makes a network request, it is always going to qualify as an async action creator.
If you want to have asynchronous action creators inside of a Redux application you have to install something called a middleware that is going to allow you to deal with those asynchronous action creators.
You can verify this in the error message that tells us use custom middleware for async actions.
So what is a middleware and why do we need it for async flow in Redux?
In the context of redux middleware such as redux-thunk, a middleware helps us deal with asynchronous action creators as that is something that Redux cannot handle out of the box.
With a middleware integrated into the Redux cycle, we are still calling action creators, that is going to return an action that will be dispatched but now when we dispatch an action, rather than sending it directly off to all of our reducers, we are going to say that an action will be sent through all the different middleware inside the application.
Inside of a single Redux app, we can have as many or as few middleware as we want. For the most part, in the projects we work on we will have one or two middleware hooked up to our Redux store.
A middleware is a plain JavaScript function that will be called with every single action that we dispatch. Inside of that function a middleware has the opportunity to stop an action from being dispatched to any of the reducers, it can modify an action or just mess around with an action in any way you which for example, we could create a middleware that console logs every action you dispatch just for your viewing pleasure.
There are a tremendous number of open source middleware you can install as dependencies into your project.
You are not limited to only making use of open source middleware or installing them as dependencies. You can write your own custom middleware and use it inside of your Redux store.
One of the more popular uses of middleware (and getting to your answer) is for dealing with asynchronous action creators, probably the most popular middleware out there is redux-thunk and it is about helping you deal with asynchronous action creators.
There are many other types of middleware that also help you in dealing with asynchronous action creators.
To use Redux-saga is the best middleware in React-redux implementation.
Ex:
store.js
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from 'redux';
import allReducer from '../reducer/allReducer';
import rootSaga from '../saga';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
allReducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga);
export default store;
And then saga.js
import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';
export function* updateLesson(){
try{
yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js
}
catch(e){
console.log("error",e)
}
}
export function* updateDetail(action) {
try{
//To write store update details
}
catch(e){
console.log("error",e)
}
}
export default function* rootSaga(){
yield [
updateLesson()
]
}
And then action.js
export default function updateFruit(props,fruit) {
return (
{
type:"UPDATE_DETAIL",
payload:fruit,
props:props
}
)
}
And then reducer.js
import {combineReducers} from 'redux';
const fetchInitialData = (state=[],action) => {
switch(action.type){
case "INITIAL_DATA":
return ({type:action.type, payload:action.payload});
break;
}
return state;
}
const updateDetailsData = (state=[],action) => {
switch(action.type){
case "INITIAL_DATA":
return ({type:action.type, payload:action.payload});
break;
}
return state;
}
const allReducers =combineReducers({
data:fetchInitialData,
updateDetailsData
})
export default allReducers;
And then main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';
const initialState = {};
const store = configureStore(initialState, browserHistory);
ReactDOM.render(
<Provider store={store}>
<App /> /*is your Component*/
</Provider>,
document.getElementById('app'));
try this.. is working
To Answer the question:
Why can't the container component call the async API, and then
dispatch the actions?
I would say for at least two reasons:
The first reason is the separation of concerns, it's not the job of the action creator to call the api and get data back, you have to have to pass two argument to your action creator function, the action type and a payload.
The second reason is because the redux store is waiting for a plain object with mandatory action type and optionally a payload (but here you have to pass the payload too).
The action creator should be a plain object like below:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
And the job of Redux-Thunk midleware to dispache the result of your api call to the appropriate action.
When working in an enterprise project, there are many requirements available in middle-ware such as (saga) not available in simple asynchronous flow, below are some:
Running request in parallel
Pulling future actions without the need to wait
Non-blocking calls Race effect, example pickup first
response to initiate the process Sequencing your tasks (first in first call)
Composing
Task cancellation Dynamically forking the task.
Support Concurrency Running Saga outside the redux middleware.
Using channels
The list is long just review the advanced section in saga documentation
Redux can't return a function instead of an action. It's just a fact. That's why people use Thunk. Read these 14 lines of code to see how it allows the async cycle to work with some added function layering:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
https://github.com/reduxjs/redux-thunk
I would say for at least two reasons:
The first reason is the separation of concerns, it's not the job of the action creator to call the api and get data back, you have to have to pass two argument to your action creator function, the action type and a payload.
The second reason is because the redux store is waiting for a plain object with mandatory action type and optionally a payload (but here you have to pass the payload too).
The action creator should be a plain object like below:
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
And the job of Redux-Thunk midleware to dispache the result of your api call to the appropriate action.
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} />;
}