React.js set callback function after execution of first function - javascript

I have been calling an api on onClick event of my React.js/Redux app. After calling the api, I have to call another api based on the results of first API. So I was thinking about async calls.
My code:
<a href="" onClick={(e) => this.sendData(e)} >Send</a>
and method sendData is
sendData(e){
e.preventDefault();
this.props.sendDataApi({this.refs.msg.value});
//After receiving results from above api, i have to call another api async
this.props.getDetails(this.refs.txt.value);
}
But currenlty both APIs called at the same time. Any help!

You should consider using async/await pattern which handles that scenario well. Your code would then look like this:
const sendData = async message => {
const messageId = await sendDataApi(message)
const details = await getDetails(messageId)
return details
}
However, if you are not able to use newest language features you may use Promises. Keep in mind that the code will be more prone to callback-hell and that they might also need polyfilling.
The same code with promises would look like this:
const sendData = message => {
sendDataApi(message)
.then(messageId => getDetails(messageId))
}
This code is a mere example of the issues with Promises because it can be reduced just to sendDataApi(message).then(getDetails). Problems arise when there are complex conditional logic, error handling, more tasks, and parameter passing.
React handles asynchrony very poorly so do the Redux. You need to use redux-thunk, redux-saga or another middleware. With that, you will be able to dispatch another action from the action creator (continuation, trunk, saga - you name it). It can become tedious, require much boilerplate, and be very difficult to debug. You may consider leaving Redux synchronous state management and consider other solutions (RxJS maybe?) for those kinds of problems.
Please see an example of using Promises to chain two asynchronous calls using React's default setState (see console output).
With redux-thunk the implementation might look like this (other actions not show, no error handling etc.):
...
const sendData = message => {
return dispatch => {
return sendDataApi(message).then(
messageId => dispatch(getDetails(messageId)),
);
};
}

Related

When to use dispatch in an action creator?

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.

About Redux Async without middleware (redux-thunk, redux-saga... )

Some action have async function like fetch. But, i don't want to use middle ware like redux-thunk or redux-saga.
So, i hesitate to use this code.
/* actions */
...
export const fetchRequest = ({category, query, dispatch}) => ({
type: actionTypes.FETCH_REQUEST,
promise:
fetch(`${API_URL}${category}?${query}`, {headers: headers})
.then(response => response.json())
.then(data => dispatch(fetchRecieve(data))),
})
export const fetchRecieve = data => ({
type: actionTypes.FETCH_RECIEVE,
data,
})
...
/* In x.jsx */
...
const mapDispatchToProps = dispatch => ({
onClick: (category, query) => dispatch(fetchRequest({category, query, dispatch}))
})
...
Is this code violated for Redux paradigm ?
Redux FAQ "How can I represent “side effects” such as AJAX calls?..
In general, Redux suggests that code with side effects should be part of the action creation process. While that logic can be performed inside of a UI component, it generally makes sense to extract that logic into a reusable function so that the same logic can be called from multiple places—in other words, an action creator function.
The functions you posted are action creators, so it does seem to be a proper place to put them, however in the next paragraph they say:
The simplest and most common way to do this is to add the Redux Thunk middleware that lets you write action creators with more complex and asynchronous logic. Another widely-used method is Redux Saga which lets you write more synchronous-looking code using generators, and can act like “background threads” or “daemons” in a Redux app. Yet another approach is Redux Loop, which inverts the process by allowing your reducers to declare side effects in response to state changes and have them executed separately. Beyond that, there are many other community-developed libraries and ideas, each with their own take on how side effects should be managed.
Is there any reason why you're opposed to using redux-thunk, redux-saga, redux-loop, etc? They all exist to help you out really. You can choose to do do side effects manually, but it's less managed that using a middleware to do it IMO.
It is totally fine to inject dispatch into your action creator and use it for whatever you need it.
Outsourcing more logic from your components into your actions (or your middleware) is actually a good thing if you plan to reuse this logic in different places. It is completely legit that the logic inside your actions may also include async operations (as in your case) that postpone a dispatch or operations that dispatch several other actions. In case of redux-thunk this is called composition.
Your solution is somehow a "shortcut" compared to the "redux-thunk-way", where your thunk would first travel through the middleware and then control will be immediately inverted by redux-thunk. With redux-thunk, you would also have the benefit of getting getState injected, besides dispatch, which gives you direct access to the entire store (without redux-thunk, you would have to resort to mergeProps in your component to have both access to the store and dispatch at the same time).
Also the binding of dispatch to your actions is more standardized with redux-thunk, which uses currying, so it will be less boilerplate code inside your mapDispatchToProps and look cleaner (since you do not have to inject dispatch yourself everytime).

Redux middleware async function

I have a redux middleware that interacts with a rest api. I recently started rewriting some fetch functions using async/await.
For this to make the most sense for me, I would need the middleware function itself to be an async function so that I can use "await" and try/catch blocks on those other async functions I have created as opposed to having to use .then, .catch.
So far I have this:
const apiMiddleware = ({
dispatch,
getState
}) => next => async action => {
switch (action.type) {
//...
}
next(action);
};
Please note the use of async keyword before "action". So far this seems to be working as expected, I am able to await other async functions from that middleware. However, since I was not able to find documentation on this, I was wondering if what I did truly is valid.
Thank you
Have you considered looking into Redux Sagas? https://redux-saga.js.org/
This is specifically created for making working with async functions more manageable.
And i would think that implementing this, can make your async functions more understandable, and easier to debug.
Your code is valid javascript, but you might experience inconsistency in the state, because of the async nature of your functions.
If your async action isn't finished before your next action is dispatched, the second action will use a version of the state, that might be mutated by the first action, while running.
This behavior can give you issues like you described.
Edit: After i learned you are already using redux-thunks
In redux thunks, you can chain actions dispatches, with await, and leveraging the return of a dispatch.
An example on how to chain actions with redux-thunks from this article: https://blog.jscrambler.com/async-dispatch-chaining-with-redux-thunk/
const dispatchChaining = () => async (dispatch) => {
await Promise.all([
dispatch(loadPosts()), // <-- async dispatch chaining in action
dispatch(loadProfile())
]);
return dispatch(updateDone());
};
const actions = redux.bindActionCreators({dispatchChaining}, store.dispatch);
actions.dispatchChaining().then(() => unsubscribe());
Note that as long as there is a return these dispatches are thenable. The bonus here is we can fire async dispatches in parallel and wait for both to finish. Then, update isDone knowing both calls are done without any unpredictable behavior. These reusable thunks can live in different parts of the store to maintain separation of concerns.

Why should I use redux async actions? [duplicate]

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.

Pros/cons of using redux-saga with ES6 generators vs redux-thunk with ES2017 async/await

There is a lot of talk about the latest kid in redux town right now, redux-saga/redux-saga. It uses generator functions for listening to/dispatching actions.
Before I wrap my head around it, I would like to know the pros/cons of using redux-saga instead of the approach below where I'm using redux-thunk with async/await.
A component might look like this, dispatch actions like usual.
import { login } from 'redux/auth';
class LoginForm extends Component {
onClick(e) {
e.preventDefault();
const { user, pass } = this.refs;
this.props.dispatch(login(user.value, pass.value));
}
render() {
return (<div>
<input type="text" ref="user" />
<input type="password" ref="pass" />
<button onClick={::this.onClick}>Sign In</button>
</div>);
}
}
export default connect((state) => ({}))(LoginForm);
Then my actions look something like this:
// auth.js
import request from 'axios';
import { loadUserData } from './user';
// define constants
// define initial state
// export default reducer
export const login = (user, pass) => async (dispatch) => {
try {
dispatch({ type: LOGIN_REQUEST });
let { data } = await request.post('/login', { user, pass });
await dispatch(loadUserData(data.uid));
dispatch({ type: LOGIN_SUCCESS, data });
} catch(error) {
dispatch({ type: LOGIN_ERROR, error });
}
}
// more actions...
// user.js
import request from 'axios';
// define constants
// define initial state
// export default reducer
export const loadUserData = (uid) => async (dispatch) => {
try {
dispatch({ type: USERDATA_REQUEST });
let { data } = await request.get(`/users/${uid}`);
dispatch({ type: USERDATA_SUCCESS, data });
} catch(error) {
dispatch({ type: USERDATA_ERROR, error });
}
}
// more actions...
In redux-saga, the equivalent of the above example would be
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST)
try {
let { data } = yield call(request.post, '/login', { user, pass });
yield fork(loadUserData, data.uid);
yield put({ type: LOGIN_SUCCESS, data });
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
The first thing to notice is that we're calling the api functions using the form yield call(func, ...args). call doesn't execute the effect, it just creates a plain object like {type: 'CALL', func, args}. The execution is delegated to the redux-saga middleware which takes care of executing the function and resuming the generator with its result.
The main advantage is that you can test the generator outside of Redux using simple equality checks
const iterator = loginSaga()
assert.deepEqual(iterator.next().value, take(LOGIN_REQUEST))
// resume the generator with some dummy action
const mockAction = {user: '...', pass: '...'}
assert.deepEqual(
iterator.next(mockAction).value,
call(request.post, '/login', mockAction)
)
// simulate an error result
const mockError = 'invalid user/password'
assert.deepEqual(
iterator.throw(mockError).value,
put({ type: LOGIN_ERROR, error: mockError })
)
Note we're mocking the api call result by simply injecting the mocked data into the next method of the iterator. Mocking data is way simpler than mocking functions.
The second thing to notice is the call to yield take(ACTION). Thunks are called by the action creator on each new action (e.g. LOGIN_REQUEST). i.e. actions are continually pushed to thunks, and thunks have no control on when to stop handling those actions.
In redux-saga, generators pull the next action. i.e. they have control when to listen for some action, and when to not. In the above example the flow instructions are placed inside a while(true) loop, so it'll listen for each incoming action, which somewhat mimics the thunk pushing behavior.
The pull approach allows implementing complex control flows. Suppose for example we want to add the following requirements
Handle LOGOUT user action
upon the first successful login, the server returns a token which expires in some delay stored in a expires_in field. We'll have to refresh the authorization in the background on each expires_in milliseconds
Take into account that when waiting for the result of api calls (either initial login or refresh) the user may logout in-between.
How would you implement that with thunks; while also providing full test coverage for the entire flow? Here is how it may look with Sagas:
function* authorize(credentials) {
const token = yield call(api.authorize, credentials)
yield put( login.success(token) )
return token
}
function* authAndRefreshTokenOnExpiry(name, password) {
let token = yield call(authorize, {name, password})
while(true) {
yield call(delay, token.expires_in)
token = yield call(authorize, {token})
}
}
function* watchAuth() {
while(true) {
try {
const {name, password} = yield take(LOGIN_REQUEST)
yield race([
take(LOGOUT),
call(authAndRefreshTokenOnExpiry, name, password)
])
// user logged out, next while iteration will wait for the
// next LOGIN_REQUEST action
} catch(error) {
yield put( login.error(error) )
}
}
}
In the above example, we're expressing our concurrency requirement using race. If take(LOGOUT) wins the race (i.e. user clicked on a Logout Button). The race will automatically cancel the authAndRefreshTokenOnExpiry background task. And if the authAndRefreshTokenOnExpiry was blocked in middle of a call(authorize, {token}) call it'll also be cancelled. Cancellation propagates downward automatically.
You can find a runnable demo of the above flow
I will add my experience using saga in production system in addition to the library author's rather thorough answer.
Pro (using saga):
Testability. It's very easy to test sagas as call() returns a pure object. Testing thunks normally requires you to include a mockStore inside your test.
redux-saga comes with lots of useful helper functions about tasks. It seems to me that the concept of saga is to create some kind of background worker/thread for your app, which act as a missing piece in react redux architecture(actionCreators and reducers must be pure functions.) Which leads to next point.
Sagas offer independent place to handle all side effects. It is usually easier to modify and manage than thunk actions in my experience.
Con:
Generator syntax.
Lots of concepts to learn.
API stability. It seems redux-saga is still adding features (eg Channels?) and the community is not as big. There is a concern if the library makes a non backward compatible update some day.
I'd just like to add some comments from my personal experience (using both sagas and thunk):
Sagas are great to test:
You don't need to mock functions wrapped with effects
Therefore tests are clean, readable and easy to write
When using sagas, action creators mostly return plain object literals. It is also easier to test and assert unlike thunk's promises.
Sagas are more powerful. All what you can do in one thunk's action creator you can also do in one saga, but not vice versa (or at least not easily). For example:
wait for an action/actions to be dispatched (take)
cancel existing routine (cancel, takeLatest, race)
multiple routines can listen to the same action (take, takeEvery, ...)
Sagas also offers other useful functionality, which generalize some common application patterns:
channels to listen on external event sources (e.g. websockets)
fork model (fork, spawn)
throttle
...
Sagas are great and powerful tool. However with the power comes responsibility. When your application grows you can get easily lost by figuring out who is waiting for the action to be dispatched, or what everything happens when some action is being dispatched. On the other hand thunk is simpler and easier to reason about. Choosing one or another depends on many aspects like type and size of the project, what types of side effect your project must handle or dev team preference. In any case just keep your application simple and predictable.
Update in July 2020:
During the last 16 months, maybe the most notable change in the React community is React hooks.
According to what I observe, in order to gain better compatibility with functional components and hooks, projects (even those large ones) would tend to use:
hook + async thunk (hook makes everything very flexible so you could actually place async thunk in where you want and use it as normal functions, for example, still write thunk in action.ts and then useDispatch() to trigger the thunk: https://stackoverflow.com/a/59991104/5256695),
useRequest,
GraphQL/Apollo useQuery useMutation
react-fetching-library
other popular choices of data fetching/API call libraries, tools, design patterns, etc
In comparison, redux-saga doesn't really provide significant benefit in most normal cases of API calls comparing to the above approaches for now, while increasing project complexity by introducing many saga files/generators (also because the last release v1.1.1 of redux-saga was on 18 Sep 2019, which was a long time ago).
But still, redux-saga provides some unique features such as racing effect and parallel requests. Therefore, if you need these special functionalities, redux-saga is still a good choice.
Original post in March 2019:
Just some personal experience:
For coding style and readability, one of the most significant advantages of using redux-saga in the past is to avoid callback hell in redux-thunk — one does not need to use many nesting then/catch anymore. But now with the popularity of async/await thunk, one could also write async code in sync style when using redux-thunk, which may be regarded as an improvement in redux-thunk.
One may need to write much more boilerplate codes when using redux-saga, especially in Typescript. For example, if one wants to implement a fetch async function, the data and error handling could be directly performed in one thunk unit in action.js with one single FETCH action. But in redux-saga, one may need to define FETCH_START, FETCH_SUCCESS and FETCH_FAILURE actions and all their related type-checks, because one of the features in redux-saga is to use this kind of rich “token” mechanism to create effects and instruct redux store for easy testing. Of course one could write a saga without using these actions, but that would make it similar to a thunk.
In terms of the file structure, redux-saga seems to be more explicit in many cases. One could easily find an async related code in every sagas.ts, but in redux-thunk, one would need to see it in actions.
Easy testing may be another weighted feature in redux-saga. This is truly convenient. But one thing that needs to be clarified is that redux-saga “call” test would not perform actual API call in testing, thus one would need to specify the sample result for the steps which may be used after the API call. Therefore before writing in redux-saga, it would be better to plan a saga and its corresponding sagas.spec.ts in detail.
Redux-saga also provides many advanced features such as running tasks in parallel, concurrency helpers like takeLatest/takeEvery, fork/spawn, which are far more powerful than thunks.
In conclusion, personally, I would like to say: in many normal cases and small to medium size apps, go with async/await style redux-thunk. It would save you many boilerplate codes/actions/typedefs, and you would not need to switch around many different sagas.ts and maintain a specific sagas tree. But if you are developing a large app with much complex async logic and the need for features like concurrency/parallel pattern, or have a high demand for testing and maintenance (especially in test-driven development), redux-sagas would possibly save your life.
Anyway, redux-saga is not more difficult and complex than redux itself, and it does not have a so-called steep learning curve because it has well-limited core concepts and APIs. Spending a small amount of time learning redux-saga may benefit yourself one day in the future.
Having reviewed a few different large scale React/Redux projects in my experience Sagas provide developers a more structured way of writing code that is much easier to test and harder to get wrong.
Yes it is a little wierd to start with, but most devs get enough of an understanding of it in a day. I always tell people to not worry about what yield does to start with and that once you write a couple of test it will come to you.
I have seen a couple of projects where thunks have been treated as if they are controllers from the MVC patten and this quickly becomes an unmaintable mess.
My advice is to use Sagas where you need A triggers B type stuff relating to a single event. For anything that could cut across a number of actions, I find it is simpler to write custom middleware and use the meta property of an FSA action to trigger it.
Thunks versus Sagas
Redux-Thunk and Redux-Saga differ in a few important ways, both are middleware libraries for Redux (Redux middleware is code that intercepts actions coming into the store via the dispatch() method).
An action can be literally anything, but if you're following best practices, an action is a plain javascript object with a type field, and optional payload, meta, and error fields. e.g.
const loginRequest = {
type: 'LOGIN_REQUEST',
payload: {
name: 'admin',
password: '123',
}, };
Redux-Thunk
In addition to dispatching standard actions, Redux-Thunk middleware allows you to dispatch special functions, called thunks.
Thunks (in Redux) generally have the following structure:
export const thunkName =
parameters =>
(dispatch, getState) => {
// Your application logic goes here
};
That is, a thunk is a function that (optionally) takes some parameters and returns another function. The inner function takes a dispatch function and a getState function -- both of which will be supplied by the Redux-Thunk middleware.
Redux-Saga
Redux-Saga middleware allows you to express complex application logic as pure functions called sagas. Pure functions are desirable from a testing standpoint because they are predictable and repeatable, which makes them relatively easy to test.
Sagas are implemented through special functions called generator functions. These are a new feature of ES6 JavaScript. Basically, execution jumps in and out of a generator everywhere you see a yield statement. Think of a yield statement as causing the generator to pause and return the yielded value. Later on, the caller can resume the generator at the statement following the yield.
A generator function is one defined like this. Notice the asterisk after the function keyword.
function* mySaga() {
// ...
}
Once the login saga is registered with Redux-Saga. But then the yield take on the the first line will pause the saga until an action with type 'LOGIN_REQUEST' is dispatched to the store. Once that happens, execution will continue.
For more details see this article.
One quick note. Generators are cancellable, async/await — not.
So for an example from the question, it does not really make sense of what to pick.
But for more complicated flows sometimes there is no better solution than using generators.
So, another idea could be is to use generators with redux-thunk, but for me, it seems like trying to invent a bicycle with square wheels.
And of course, generators are easier to test.
To give this answer some context: Hi, I'm a Redux maintainer.
We recently added a new Side Effects Approaches page to the Redux documentation that should give a lot of information on all of this, but I'll try to write something short here too, as this question gets a lot of exposure.
In 2022 we added the Listener Middleware to the official Redux Toolkit for "reactive Redux logic". It can do most things that sagas can (the exception being channels) without requiring generator syntax and with better TypeScript support.
That doesn't mean that you should write everything with the listener middleware, though - we recommend always going for thunks first where possible and using the listener middleware in addition where thunks cannot do what you want to do.
Generally, our stance as of 2023 is that you should only use sagas if you have a particular need that cannot be met by other middleware. (Essentially: if you need channels.)
Our recommendation is:
Data Fetching
Use RTK Query as the default approach for data fetching and caching
If RTKQ doesn't fully fit for some reason, use createAsyncThunk
Only fall back to handwritten thunks if nothing else works
Don't use sagas or observables for data fetching!
Reacting to Actions / State Changes, Async Workflows
Use RTK listeners as the default for responding to store updates and writing long-running async workflows
Only use sagas / observables if listeners don't solve your use case well enough
Logic with State Access
Use thunks for complex sync and moderate async logic, including access to getState and dispatching multiple actions
Here's a project that combines the best parts (pros) of both redux-saga and redux-thunk: you can handle all side-effects on sagas while getting a promise by dispatching the corresponding action:
https://github.com/diegohaz/redux-saga-thunk
class MyComponent extends React.Component {
componentWillMount() {
// `doSomething` dispatches an action which is handled by some saga
this.props.doSomething().then((detail) => {
console.log('Yaay!', detail)
}).catch((error) => {
console.log('Oops!', error)
})
}
}
I have recently joined a project that makes heavy use of redux-saga, and so was also interested in finding out more about the benefits of the saga approach.
TBH, I am still looking. Having read this post and many like it, the 'pros' are elusive. The above answers seem to sum it up as:
testability (ignoring actual API calls),
lots of helper functions,
familiarity for developers who are used to server-side coding.
Many other claims seem optimistic, misleading or simply false! I've seen many unjustified claims that "thunks cannot do X" for example. But thunks are functions. If a function cannot do X, then javascript cannot do X. So sagas cannot do X either.
For me, the CONS are:
confounding of concerns by using generator functions. Generators in JS return custom iterators. That is all. They do not have any special ability to handle async calls or to be cancellable. Any loop can have a break-out condition, any function can handle async requests, and any code can make use of a custom iterator. When people say thing like: generators have control when to listen for some action or generators are cancellable, but async calls are not then it creates confusion by implying that these qualities are inherent in - or even unique to - generator functions.
unclear use-cases: AFAIK the SAGA pattern is for handling concurrent transaction issues across services. Given that browsers are single-threaded, it is hard to see how concurrency presents a problem that Promise methods can't handle. BTW: it is also hard to see why that class of problem should ever be handled in the browser.
code traceability: By using redux middleware to turn dispatch into a kind of event-handling, Sagas dispatch actions that never reach the reducers, and so never get logged by Redux tools. While other libraries also do this, it is often unnecessarily complicated, given that browsers have event-handling built in. The advantage of the indirection is again elusive, when calling the saga directly would be more obvious.
If this post makes me seem frustrated with sagas, it is because I am frustrated with sagas. They seem like a great solution looking for a problem to solve. IMO.
An easier way is to use redux-auto.
from the documantasion
redux-auto fixed this asynchronous problem simply by allowing you to create an "action" function that returns a promise. To accompany your "default" function action logic.
No need for other Redux async middleware. e.g. thunk, promise-middleware, saga
Easily allows you to pass a promise into redux and have it managed for you
Allows you to co-locate external service calls with where they will be transformed
Naming the file "init.js" will call it once at app start. This is good for loading data from the server at start
The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.
It also automatically attaches a helper object(called "async") to the prototype of your state, allowing you to track in your UI, requested transitions.

Categories