New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simpler introduction to async action creators #533

Closed
danmaz74 opened this Issue Aug 15, 2015 · 25 comments

Comments

@danmaz74
Contributor

danmaz74 commented Aug 15, 2015

I asked someone from my team to study the docs about async actions, and he found them very hard to follow. This confirmed my idea that there should be a simpler introduction about this fundamental aspect of using Redux before going into all the details about middleware, how it works etc.

I think a good approach could be to add one last chapter to the "Basics" part of the docs, with a practical explanation about using async. To explain it to my team member I used a simple example, and now decided to write it down in a way that could be used as the base for this new chapter; you can find it below - it's not 100% polished (and not actually tested) because I'm not sure you'll want to use it. If not interested let me know, and I'll recycle it as a blog post :)

Adding async actions

In Redux, basic action creators are pure functions with no side effects. This means that they can't directly execute any async code, as the Flux architecture instead suggests, because async code has side effects by definition.

Fortunately, Redux comes with a powerful middleware system and one specific middleware library, redux-thunk, that allows us to integrate async calls into our action creators in a clean and easy way. This is just a short practical introduction, but you can learn the full story here [link to Advanced chapter].

First of all, we need to integrate the redux-thunk middleware into our store. We can do this in our Todo List example by changing some lines inside index.js; from this:

import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './containers/App';
import todoApp from './reducers';

let store = createStore(todoApp);

To this:

import React from 'react';
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import App from './containers/App';
import todoApp from './reducers';

let store =  applyMiddleware(thunk)(createStore)(todoApp);

Before this change, store.dispatch could only accept a simple Action object. With redux-thunk, dispatch can also accept a thunk function.

  • If dispatch receives an Action object, redux-thunk will do nothing, and the reducers will get called with the action as usual, and change the state synchronously.
  • If instead dispatch receives a thunk function, redux-thunk will execute it. It will be the thunk function's responsibility to actually dispatch an action object. More importantly, the thunk function doesn't need to be pure, so it can contain async calls, and can dispatch some (synchronous) action only after the async call has finished, also using the data received from the call. Let's see this with a simple example for our Todo app.

In a real app, we'll want to keep our Todo list synchronized with our server. For now we'll only focus on the addTodo action creator; the current one will only update the client side state, but instead we want it to:

  • Optimistically update the local state and UI
  • Send the data to the server, so it can update the DB
  • Manage the server response

To achieve this, we still need our "optimistic" addTodo action creator, but we also need an async thunk action creator to handle the whole async process. To keep the changes to the code at a minimum, we'll rename the optimistic action creator, but not the action type that is sent to the reducers (and used in the switch statement to act). This goes into actions.js:

// renamed optimistic action creator - this won't be called directly 
// by the React components anymore, but from our async thunk function
export function addTodoOptimistic(text) {
  return { type: ADD_TODO, text };
}

// the async action creator uses the name of the old action creator, so 
// it will get called by the existing code when a new todo item should 
//  be added
export function addTodo(text) {
  // we return a thunk function, not an action object!
  // the thunk function needs to dispatch some actions to change the 
  // Store status, so it receives the "dispatch" function as its first parameter
  return function(dispatch) {
    // here starts the code that actually gets executed when the addTodo action 
    // creator is dispatched

    // first of all, let's do the optimistic UI update - we need to 
    // dispatch the old synchronous action object, using the renamed 
    // action creator
    dispatch(addTodoOptimistic(text));

    // now that the Store has been notified of the new todo item, we 
    // should also notify our server - we'll use here ES6 fetch function 
    // to post the data
    fetch('/add_todo', {
      method: 'post',
      body: JSON.stringify({
        text
      })
    }).then(response => {
      // you should probably get a real id for your new todo item here, 
      // and update your store, but we'll leave that to you
    }).catch(err => {
    // Error: handle it the way you like, undoing the optimistic update,
    //  showing a "out of sync" message, etc.
    });
  // what you return here gets returned by the dispatch function that used   
  // this action creator
  return null; 
  }
}

The above code and its comments show how you can do async calls, mixing them with optimistic updates, while still using the same old dispatch syntax with an action creator. 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. If needed, the thunk can also access the current store state, as it gets getState as its second argument: return function(dispatch, getState) { ...

Redux-thunk is only one example of what middleware can do; to learn more, proceed to the advanced tutorial [link]

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Aug 15, 2015

Contributor

Hi, thanks for the feedback!

Isn't this more or less what async actions section talks about?
Do you find it hard to digest?

Contributor

gaearon commented Aug 15, 2015

Hi, thanks for the feedback!

Isn't this more or less what async actions section talks about?
Do you find it hard to digest?

@gaearon gaearon added the docs label Aug 15, 2015

@danmaz74

This comment has been minimized.

Show comment
Hide comment
@danmaz74

danmaz74 Aug 15, 2015

Contributor

You're welcome Dan.

Yes I'm not adding anything to what you already explain, but I think there should be a much simpler "how-to" about async inside the Basics section - async requests/updates are a basic requirement for every web app.

The current Advanced section of the gitbook reads much more like a University book than a simple howto - learning the reasons that brought you to choose this implementation of middleware, or why actions can't express control flow, is interesting, but it isn't (and shouldn't be) necessary to learn how to do a simple async call.

Personally I found the async actions page interesting but a bit slow to get to the point - the actual explanation for Async Action Creators starts after three fourths of the page - but the most important point is that my team member really found it almost impossible to digest. I suspect more than a few programmers could get discouraged by the current middleware+async explanation.

Anyway, feel free to ask what others think, but don't think only about people as smart as you are - at least if you want the library to get very wide adoption ;)

Contributor

danmaz74 commented Aug 15, 2015

You're welcome Dan.

Yes I'm not adding anything to what you already explain, but I think there should be a much simpler "how-to" about async inside the Basics section - async requests/updates are a basic requirement for every web app.

The current Advanced section of the gitbook reads much more like a University book than a simple howto - learning the reasons that brought you to choose this implementation of middleware, or why actions can't express control flow, is interesting, but it isn't (and shouldn't be) necessary to learn how to do a simple async call.

Personally I found the async actions page interesting but a bit slow to get to the point - the actual explanation for Async Action Creators starts after three fourths of the page - but the most important point is that my team member really found it almost impossible to digest. I suspect more than a few programmers could get discouraged by the current middleware+async explanation.

Anyway, feel free to ask what others think, but don't think only about people as smart as you are - at least if you want the library to get very wide adoption ;)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Aug 15, 2015

Contributor

don't think only about people as smart as you are - at least if you want the library to get very wide adoption

When I'm bad at explaining something, it's not on purpose. ;-)

The current Advanced section of the gitbook reads much more like a University book than a simple howto - learning the reasons that brought you to choose this implementation of middleware, or why actions can't express control flow, is interesting, but it isn't (and shouldn't be) necessary to learn how to do a simple async call.

I agree. But I don't want people to think redux-thunk is the way to do async calls, or that there is some special meaning assigned to dispatching functions in Redux.

Personally I found the async actions page interesting but a bit slow to get to the point - the actual explanation for Async Action Creators starts after three fourths of the page

I'd appreciate you suggesting how to rearrange that existing doc. Regarding “a bit slow to get to the point”: what's the use teaching to dispatch thunks when the user doesn't yet understand that separate actions correspond to success/failure, or how to represent async app state? Even if they learn about dispatching async actions, they'll have no idea how to handle them. I tried to approach it from the other way: introduce asynchrony at the very end, showing it as mere syntax sugar over the foundation patterns.

I don't know. I understand where your criticism is coming from, and indeed, the doc turned out long-winded; on the other hand, I've heard many thanks for writing them in a booky fashion because many tutorials just present the APIs without explaining how to use them in app (how to choose actions, how to design state shape, how to handle pagination, etc). These are the kind of practical things I find more important to show than a “generic” code snippet.

I hope you understand why I'm torn on this issue. Is there any way we can rearrange the sections in advanced tutorial to help your concerns?

Contributor

gaearon commented Aug 15, 2015

don't think only about people as smart as you are - at least if you want the library to get very wide adoption

When I'm bad at explaining something, it's not on purpose. ;-)

The current Advanced section of the gitbook reads much more like a University book than a simple howto - learning the reasons that brought you to choose this implementation of middleware, or why actions can't express control flow, is interesting, but it isn't (and shouldn't be) necessary to learn how to do a simple async call.

I agree. But I don't want people to think redux-thunk is the way to do async calls, or that there is some special meaning assigned to dispatching functions in Redux.

Personally I found the async actions page interesting but a bit slow to get to the point - the actual explanation for Async Action Creators starts after three fourths of the page

I'd appreciate you suggesting how to rearrange that existing doc. Regarding “a bit slow to get to the point”: what's the use teaching to dispatch thunks when the user doesn't yet understand that separate actions correspond to success/failure, or how to represent async app state? Even if they learn about dispatching async actions, they'll have no idea how to handle them. I tried to approach it from the other way: introduce asynchrony at the very end, showing it as mere syntax sugar over the foundation patterns.

I don't know. I understand where your criticism is coming from, and indeed, the doc turned out long-winded; on the other hand, I've heard many thanks for writing them in a booky fashion because many tutorials just present the APIs without explaining how to use them in app (how to choose actions, how to design state shape, how to handle pagination, etc). These are the kind of practical things I find more important to show than a “generic” code snippet.

I hope you understand why I'm torn on this issue. Is there any way we can rearrange the sections in advanced tutorial to help your concerns?

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Aug 15, 2015

Contributor

I asked someone from my team to study the docs about async actions, and he found them very hard to follow.

I think some feedback on what exactly was hard to follow would be very much appreciated. Is the order bad? Should we introduce something earlier, and something later? Is some specific section incomprehensible? Is some code snippet badly documented, or not explained?

The result of basic tutorial is a finished application. I'm still not convinced that async actions fit there. This introduces many new concepts right at the end, regardless of how "basic" that requirement is—and don't forget the person just learned about the unidirectional data flow! We might as well give them some rest and satisfaction.

Why put async actions right at the end of basic tutorial if they are the only reason the advanced tutorial even exists? Why not just put "Async Actions" first in the advanced tutorial? (And rearrange it to make it more accessible.)

Contributor

gaearon commented Aug 15, 2015

I asked someone from my team to study the docs about async actions, and he found them very hard to follow.

I think some feedback on what exactly was hard to follow would be very much appreciated. Is the order bad? Should we introduce something earlier, and something later? Is some specific section incomprehensible? Is some code snippet badly documented, or not explained?

The result of basic tutorial is a finished application. I'm still not convinced that async actions fit there. This introduces many new concepts right at the end, regardless of how "basic" that requirement is—and don't forget the person just learned about the unidirectional data flow! We might as well give them some rest and satisfaction.

Why put async actions right at the end of basic tutorial if they are the only reason the advanced tutorial even exists? Why not just put "Async Actions" first in the advanced tutorial? (And rearrange it to make it more accessible.)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Aug 15, 2015

Contributor

We can rename "basics" to "sync" and "advanced" to "async" if that helps. :-)

That was the intention of these tutorials. I really don't want to mix them because they describe two different ideas. One is central to Redux, and another builds on top of it.

Contributor

gaearon commented Aug 15, 2015

We can rename "basics" to "sync" and "advanced" to "async" if that helps. :-)

That was the intention of these tutorials. I really don't want to mix them because they describe two different ideas. One is central to Redux, and another builds on top of it.

@danmaz74

This comment has been minimized.

Show comment
Hide comment
@danmaz74

danmaz74 Aug 16, 2015

Contributor

Hi Dan

First of all, rest assured that I appreciate all the effort you put into writing the detailed docs (not to mention this whole project) - and I know from personal experience how hard it is, especially when you can't write it for a specific readership that you already know well. I also understand why you're torn here; also, there is no "only good way" to do this :)

Anyway, from your latest comments, I understand that there are two important points where we have different assumptions.

The first is that, when I think about the "average reader" of the docs, I imagine someone who already knows the basics of the Flux architecture, and is looking for a specific framework to work with. This of course won't always be true, especially if Redux becomes so big that people will come straight to it without even learning about Flux first, but from intuition I would expect most of the readers, at least for the next few months, to be like that. So it would make sense to have some shortcuts for them to understand Redux.

The second, more fundamental difference, is that from what I read I guess that you value a lot showing right from the start how Redux is flexible and doesn't force you to do things one specific way (ie, it's not an opinionated framework). Instead from my experience from teaching people, I value a lot first showing the "default way" to do things, and only later - after the default is understood - introducing variations/alternatives that can create confusion in the mind of those who are learning something for the first time. Giving too many alternatives can easily backfire - maybe you already heard about the "paradox of choice"?

If I wrote the docs myself I would then:

  • Introduce a "default way to do things" first, and explain the motivations (for those defaults/choices) and the alternatives later; in a way, it's an application of the inverted pyramid style of writing articles
  • Assume that most readers already have a basic idea of the Flux architecture, and have a "shortcut version" for them, or, on the contrary, have a shorter version as the default, and leave the introduction to the Flux concepts to a specific page [Introduction to Flux] or an external resource, saying something like: if you already know Flux, you can skip this, otherwise it is required reading

But considering all the effort you already put into the docs - which are very complete, only a bit difficult to understand - and our different assumptions, I agree with you that putting "Async actions" and "Async flow" before "Middleware" in the "Advanced" section would be the easiest win. You can just say that you use this "redux-thunk" middleware and what it does, leaving the explanation of how it does it to the later chapter. The Middleware chapter is really difficult to digest for those who don't already understand the concepts of functional programming, and it's not strictly necessary to understand it to just use a ready-to-use middleware.

If then you'd also like to talk about the base assumptions, feel free to comment on them ;)

Contributor

danmaz74 commented Aug 16, 2015

Hi Dan

First of all, rest assured that I appreciate all the effort you put into writing the detailed docs (not to mention this whole project) - and I know from personal experience how hard it is, especially when you can't write it for a specific readership that you already know well. I also understand why you're torn here; also, there is no "only good way" to do this :)

Anyway, from your latest comments, I understand that there are two important points where we have different assumptions.

The first is that, when I think about the "average reader" of the docs, I imagine someone who already knows the basics of the Flux architecture, and is looking for a specific framework to work with. This of course won't always be true, especially if Redux becomes so big that people will come straight to it without even learning about Flux first, but from intuition I would expect most of the readers, at least for the next few months, to be like that. So it would make sense to have some shortcuts for them to understand Redux.

The second, more fundamental difference, is that from what I read I guess that you value a lot showing right from the start how Redux is flexible and doesn't force you to do things one specific way (ie, it's not an opinionated framework). Instead from my experience from teaching people, I value a lot first showing the "default way" to do things, and only later - after the default is understood - introducing variations/alternatives that can create confusion in the mind of those who are learning something for the first time. Giving too many alternatives can easily backfire - maybe you already heard about the "paradox of choice"?

If I wrote the docs myself I would then:

  • Introduce a "default way to do things" first, and explain the motivations (for those defaults/choices) and the alternatives later; in a way, it's an application of the inverted pyramid style of writing articles
  • Assume that most readers already have a basic idea of the Flux architecture, and have a "shortcut version" for them, or, on the contrary, have a shorter version as the default, and leave the introduction to the Flux concepts to a specific page [Introduction to Flux] or an external resource, saying something like: if you already know Flux, you can skip this, otherwise it is required reading

But considering all the effort you already put into the docs - which are very complete, only a bit difficult to understand - and our different assumptions, I agree with you that putting "Async actions" and "Async flow" before "Middleware" in the "Advanced" section would be the easiest win. You can just say that you use this "redux-thunk" middleware and what it does, leaving the explanation of how it does it to the later chapter. The Middleware chapter is really difficult to digest for those who don't already understand the concepts of functional programming, and it's not strictly necessary to understand it to just use a ready-to-use middleware.

If then you'd also like to talk about the base assumptions, feel free to comment on them ;)

@danmaz74

This comment has been minimized.

Show comment
Hide comment
@danmaz74

danmaz74 Aug 20, 2015

Contributor

I published the example on my blog: http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/

I didn't close the issue in case you want to use it as a reminder to move the "async" chapter before the "middleware" one, but feel free to close it if you're not interested in doing that.

Contributor

danmaz74 commented Aug 20, 2015

I published the example on my blog: http://danmaz74.me/2015/08/19/from-flux-to-redux-async-actions-the-easy-way/

I didn't close the issue in case you want to use it as a reminder to move the "async" chapter before the "middleware" one, but feel free to close it if you're not interested in doing that.

@timuric

This comment has been minimized.

Show comment
Hide comment
@timuric

timuric Aug 26, 2015

I agree with @danmaz74 in regards of flipping the order of chapters. After reading Middleware section it was harder to digest Async because there were too many details on a plate. Even though the docs are well written, have clear connection between the steps and a "how to" guide, it still felt esoteric to me. Probably because Flux is a new subject to me.

Here are few things that bothered me:

  • The fact that dispatch function accepts only plain objects. It would be simpler to grasp if dispatch would also accept functions that would either return objects or functions that would accept dispatch as a parameter. Otherwise to do so there is a need for extra middleware boilerplate. Middleware is a neat concept, but when you are new to Flux story, adding extra detail to the equation is explosive to your brain.
  • The reason for cascade of the middleware being so deep store => next => action instead of being just (store, next) => action. I am sure there is a good reason for that, but it was not obvious to me (probably I missed something important)
  • The use of thunk library felt intimidating at first, because it is a separate dependency it made me imagine complex story behind it. I didn't expected simple helper like this to exist in a separate repo.
  • In Middleware section the tutorial explains how to avoid monkeypatching store’s dispatch function, but the end result is still a modified store.dispatch function as in previous examples. Overall the Middleware section was the biggest struggle to read.

I am aware that these are ignorant concerns, and I have to gain better understanding of the environment. But hopefully it is useful for you to know what kind of reflection a novice person like me has :)

timuric commented Aug 26, 2015

I agree with @danmaz74 in regards of flipping the order of chapters. After reading Middleware section it was harder to digest Async because there were too many details on a plate. Even though the docs are well written, have clear connection between the steps and a "how to" guide, it still felt esoteric to me. Probably because Flux is a new subject to me.

Here are few things that bothered me:

  • The fact that dispatch function accepts only plain objects. It would be simpler to grasp if dispatch would also accept functions that would either return objects or functions that would accept dispatch as a parameter. Otherwise to do so there is a need for extra middleware boilerplate. Middleware is a neat concept, but when you are new to Flux story, adding extra detail to the equation is explosive to your brain.
  • The reason for cascade of the middleware being so deep store => next => action instead of being just (store, next) => action. I am sure there is a good reason for that, but it was not obvious to me (probably I missed something important)
  • The use of thunk library felt intimidating at first, because it is a separate dependency it made me imagine complex story behind it. I didn't expected simple helper like this to exist in a separate repo.
  • In Middleware section the tutorial explains how to avoid monkeypatching store’s dispatch function, but the end result is still a modified store.dispatch function as in previous examples. Overall the Middleware section was the biggest struggle to read.

I am aware that these are ignorant concerns, and I have to gain better understanding of the environment. But hopefully it is useful for you to know what kind of reflection a novice person like me has :)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Aug 26, 2015

Contributor

I'm happy to accept a PR doing the section reshuffle as described above.
I currently don't have time to do this.

Contributor

gaearon commented Aug 26, 2015

I'm happy to accept a PR doing the section reshuffle as described above.
I currently don't have time to do this.

@danmaz74

This comment has been minimized.

Show comment
Hide comment
@danmaz74

danmaz74 Aug 26, 2015

Contributor

I'll try to do the reshuffle when (if) I find the time. But if anybody else wants to volunteer, they're welcome :)

Contributor

danmaz74 commented Aug 26, 2015

I'll try to do the reshuffle when (if) I find the time. But if anybody else wants to volunteer, they're welcome :)

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Sep 4, 2015

Contributor

Done now via #645.

Contributor

gaearon commented Sep 4, 2015

Done now via #645.

@gaearon gaearon closed this Sep 4, 2015

@Tjorriemorrie

This comment has been minimized.

Show comment
Hide comment
@Tjorriemorrie

Tjorriemorrie Feb 24, 2016

In Middleware section the tutorial explains how to avoid monkeypatching store’s dispatch function, but the end result is still a modified store.dispatch function as in previous examples. Overall the Middleware section was the biggest struggle to read.
+1

Can I call my async action from inside a reducer? If so, must I do this calling dispatch (which is not in the reducer file), wtf this is so confusing :(

Tjorriemorrie commented Feb 24, 2016

In Middleware section the tutorial explains how to avoid monkeypatching store’s dispatch function, but the end result is still a modified store.dispatch function as in previous examples. Overall the Middleware section was the biggest struggle to read.
+1

Can I call my async action from inside a reducer? If so, must I do this calling dispatch (which is not in the reducer file), wtf this is so confusing :(

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 24, 2016

Contributor

Can I call my async action from inside a reducer?

No. Reducers are passive. They cannot “do” things, they only react to actions that have already happened. Here’s a 4 line guide to doing AJAX requests in Redux:

// sync dispatch
this.props.dispatch({ type: 'INCREMENT' })

// async dispatch
fetchSomeData().then(data => {
  this.props.dispatch({ type: 'RECEIVE_DATA', data })
});

That’s it. There’s nothing special about async dispatches that makes them so different from sync dispatches. You just dispatch when the data is available.

You might find that this pattern gets repetitive and you want to abstract the repetitive code away. This is when you might want to learn about async middleware, not before. This is a big omission in the docs but alas, I don’t have the time to rewrite them now.

Some other pointers:

Don’t worry about that “Middleware” document. It it not an introduction to async actions.

Contributor

gaearon commented Feb 24, 2016

Can I call my async action from inside a reducer?

No. Reducers are passive. They cannot “do” things, they only react to actions that have already happened. Here’s a 4 line guide to doing AJAX requests in Redux:

// sync dispatch
this.props.dispatch({ type: 'INCREMENT' })

// async dispatch
fetchSomeData().then(data => {
  this.props.dispatch({ type: 'RECEIVE_DATA', data })
});

That’s it. There’s nothing special about async dispatches that makes them so different from sync dispatches. You just dispatch when the data is available.

You might find that this pattern gets repetitive and you want to abstract the repetitive code away. This is when you might want to learn about async middleware, not before. This is a big omission in the docs but alas, I don’t have the time to rewrite them now.

Some other pointers:

Don’t worry about that “Middleware” document. It it not an introduction to async actions.

@Tjorriemorrie

This comment has been minimized.

Show comment
Hide comment
@Tjorriemorrie

Tjorriemorrie Feb 25, 2016

Thanks for answering. I'm still not a 100% sure how to structure this, e.g.

I have an input which I want to persist in the store.
As the user types, I wish to fetch autocomplete suggestions from a server.

How do you suggest I do that? As I have to store the input first, then fetch suggestions. Considering I assume from your response that I need to dispatch the async from the container on input changed, but the container is generated by connect() - there's no props/store? Do I need to dispatch two actions on the handler, e.g.?

const mapDispatchToProps = (dispatch) => {
    return {
        onChange: (text, place) => {
            dispatch(changeAddress(text, place))
            dispatch(fetchSuggestions(text)                 <--
        }
    }
};

if that is the case, does that mean that you cannot chain actions (or create reactions (Except for when you use async middleware))?

Tjorriemorrie commented Feb 25, 2016

Thanks for answering. I'm still not a 100% sure how to structure this, e.g.

I have an input which I want to persist in the store.
As the user types, I wish to fetch autocomplete suggestions from a server.

How do you suggest I do that? As I have to store the input first, then fetch suggestions. Considering I assume from your response that I need to dispatch the async from the container on input changed, but the container is generated by connect() - there's no props/store? Do I need to dispatch two actions on the handler, e.g.?

const mapDispatchToProps = (dispatch) => {
    return {
        onChange: (text, place) => {
            dispatch(changeAddress(text, place))
            dispatch(fetchSuggestions(text)                 <--
        }
    }
};

if that is the case, does that mean that you cannot chain actions (or create reactions (Except for when you use async middleware))?

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Feb 25, 2016

Contributor

I don’t understand what is being asked. Yes, you would dispatch two actions. I can’t see why you wouldn’t be able to “chain” them. If the questions on SO don’t help please create a new question with a complete code example and post a link here so I can answer it.

Contributor

gaearon commented Feb 25, 2016

I don’t understand what is being asked. Yes, you would dispatch two actions. I can’t see why you wouldn’t be able to “chain” them. If the questions on SO don’t help please create a new question with a complete code example and post a link here so I can answer it.

@andrewjmead

This comment has been minimized.

Show comment
Hide comment
@andrewjmead

andrewjmead Mar 31, 2016

Hey @gaearon. Thanks for all your great contributions.

In terms of a concrete suggestion, I would love to see an async example of POSTing data to an HTTP endpoint. Right now all the requests in the docs are GET requests (I understand it's easier to find public API where a GET request is available).

I have no problem with the general redux concepts, but I am having a hard time coming up with a good way to handle the addition of an item (for example, POSTing a new todo to the endpoint).

Since my reducer generates defaults for the todos item, I can't make my ajax request in a thunked action.

Maybe the problem is that my defaults (such as completed being false) are set in the reducer when that's probably not the best place. I copied that form you 30 video series.

andrewjmead commented Mar 31, 2016

Hey @gaearon. Thanks for all your great contributions.

In terms of a concrete suggestion, I would love to see an async example of POSTing data to an HTTP endpoint. Right now all the requests in the docs are GET requests (I understand it's easier to find public API where a GET request is available).

I have no problem with the general redux concepts, but I am having a hard time coming up with a good way to handle the addition of an item (for example, POSTing a new todo to the endpoint).

Since my reducer generates defaults for the todos item, I can't make my ajax request in a thunked action.

Maybe the problem is that my defaults (such as completed being false) are set in the reducer when that's probably not the best place. I copied that form you 30 video series.

@vtambourine

This comment has been minimized.

Show comment
Hide comment
@vtambourine

vtambourine Mar 31, 2016

@andrewjmead, you probably need to slightly change the API middleware to support custom REST verbs in it.

I had the same problem as you, but I borrowed code form real-world example from main repo. In my project I've added method argument to callApi helper. That make possible to fire any request type from actions, like POST. (Sorry for style mess in the code, it is under heavy development).

vtambourine commented Mar 31, 2016

@andrewjmead, you probably need to slightly change the API middleware to support custom REST verbs in it.

I had the same problem as you, but I borrowed code form real-world example from main repo. In my project I've added method argument to callApi helper. That make possible to fire any request type from actions, like POST. (Sorry for style mess in the code, it is under heavy development).

@kwhitejr

This comment has been minimized.

Show comment
Hide comment
@kwhitejr

kwhitejr Jun 17, 2016

Thanks for this simplified tutorial in redux-thunk. You helped me break down many of the assumptions built into other tutorials. Very helpful.

kwhitejr commented Jun 17, 2016

Thanks for this simplified tutorial in redux-thunk. You helped me break down many of the assumptions built into other tutorials. Very helpful.

@tofaelahmed

This comment has been minimized.

Show comment
Hide comment
@tofaelahmed

tofaelahmed Nov 11, 2016

Thanks for the example. What I still don't understand is how can I rollback my optimistic update when an api call fails ? I am not sure if my question falls under the scope of this issue, but can you please tell what could be an efficient way to do that ?

tofaelahmed commented Nov 11, 2016

Thanks for the example. What I still don't understand is how can I rollback my optimistic update when an api call fails ? I am not sure if my question falls under the scope of this issue, but can you please tell what could be an efficient way to do that ?

@TrySound

This comment has been minimized.

Show comment
Hide comment
@TrySound

TrySound Nov 11, 2016

Contributor

@tofaelahmed

// Update temporary
dispatch({
  type: 'REQUEST'
})
action().then(() => {
  // Do not change prev stuff, apply result
  dispatch({
    type: 'SUCCESS'
  })
}).catch(() => {
  // rollback
  dispatch({
    type: 'FAILURE'
  });
});
Contributor

TrySound commented Nov 11, 2016

@tofaelahmed

// Update temporary
dispatch({
  type: 'REQUEST'
})
action().then(() => {
  // Do not change prev stuff, apply result
  dispatch({
    type: 'SUCCESS'
  })
}).catch(() => {
  // rollback
  dispatch({
    type: 'FAILURE'
  });
});
@danmaz74

This comment has been minimized.

Show comment
Hide comment
@danmaz74

danmaz74 Nov 11, 2016

Contributor

@tofaelahmed: I'll just add to what @TrySound wrote that, very often, instead of managing every single roll-back situation - which would be awesome but can require too much work - you can simply go into a "failure state" that will alert the user and suggest (or require) to reload the page before proceeding.

Contributor

danmaz74 commented Nov 11, 2016

@tofaelahmed: I'll just add to what @TrySound wrote that, very often, instead of managing every single roll-back situation - which would be awesome but can require too much work - you can simply go into a "failure state" that will alert the user and suggest (or require) to reload the page before proceeding.

@bmagic

This comment has been minimized.

Show comment
Hide comment
@bmagic

bmagic Dec 11, 2016

👍 Your example was really helpful for me !!!

bmagic commented Dec 11, 2016

👍 Your example was really helpful for me !!!

@neti123

This comment has been minimized.

Show comment
Hide comment
@neti123

neti123 Jul 31, 2017

We should make things simpler - redux thunk, redux, redux saga, redux logic. Get rid of all of them. They are ruining the language.

neti123 commented Jul 31, 2017

We should make things simpler - redux thunk, redux, redux saga, redux logic. Get rid of all of them. They are ruining the language.

@dcousens

This comment has been minimized.

Show comment
Hide comment
@dcousens

dcousens Aug 21, 2018

Is there any aversion to simply passing the dispatch through?

export function likeAsyncRequest (dispatch) {
  dispatch({ type: 'LIKE_REQUEST' })

  setTimeout(() => {
    dispatch({ type: 'LIKE_SUCCESS' })
  }, 200)
}

// ...

function mapDispatchToProps (dispatch) {
  return {
    onClick: () => likeAsyncRequest(dispatch)
  }
}

edit: The answer is, you don't have getState

dcousens commented Aug 21, 2018

Is there any aversion to simply passing the dispatch through?

export function likeAsyncRequest (dispatch) {
  dispatch({ type: 'LIKE_REQUEST' })

  setTimeout(() => {
    dispatch({ type: 'LIKE_SUCCESS' })
  }, 200)
}

// ...

function mapDispatchToProps (dispatch) {
  return {
    onClick: () => likeAsyncRequest(dispatch)
  }
}

edit: The answer is, you don't have getState

@markerikson

This comment has been minimized.

Show comment
Hide comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment