Reasons why I selected thunks instead of sagas as the default async middleware
I was recently asked if I plan on supporting anything besides thunks for async logic and side effects in Redux Toolkit.
At the moment, I only plan on having explicit support for thunks, because:
- They are the most widely used Redux async middleware
- They handle the most common case of "I want to fetch some data"
- We specifically recommend them as the default approach for async logic
That doesn't mean you can't use other async middleware.
configureStore specifically has
enhancer arguments, allowing you to add whatever async middleware you want as part of your store setup, same as the base
FWIW, I've used sagas before and think they're a great power tool for complex async logic. I just don't think they're the right tool to be forcing on folks as a default, and most apps don't need them.
Note that the new
createAsyncThunk alpha API specifically generates those async lifecycle action types for you. I suppose you could use that with sagas too, by calling it and reusing the exposed action creators / types.
I've seen folks argue that sagas are "simpler" than thunks. I agree that sagas are much more powerful than thunks, but completely disagree on "simpler".
Thunks are a bit weird to wrap your mind around at first ("I pass a function to
dispatch? which then calls it and gives me back
dispatch again? and I have to write a function that returns a function?"). But, the middleware is only about 10 lines long, so if you look at it once or twice you get what it's doing. Once you use that pattern a couple times it's repeatable, and from there you can write whatever logic you want. Having
async/await in particular makes writing async thunks a lot easier to deal with.
With sagas, you have to:
- Understand what generator functions are and the declaration syntax
- Understand what the
- Make sure you call
- Write extra action types just to kick off the actual saga logic
- Split your saga logic into "watchers" that listen for a given action type, and 'workers" that do the actual behavior-
- Read and understand the list of Redux-Saga effects like
- Understand that these effects aren't actually doing anything themselves
- Deal with nuances of yielding different types of values in sagas
- Grok the saga-specific forking model
And on top of that, from what I've read, sagas and TypeScript don't currently go together well.
Again, I like sagas. I think they're a wonderful tool to have available. Sagas are great for very complex async workflows. In an app I built a few years ago, we had to kick off a bunch of requests that started ongoing calculations on the server, check on the server's completion status for each one, kick off more batches of requests, handle pausing the sequence, handle jumping ahead in the sequence, cancel requests if needed, and so on. No way we could have done that with thunks.
I also get the arguments about sagas being more testable, more declarative, etc. Also valid points.
But for just doing your typical CRUD fetches? Complete overkill. The amount of code you have to write to make that happen, and the number of concepts you have to deal with, make them the wrong choice for that kind of use case.
My goal with RTK is to simplify the most common use cases that users are dealing with, and provide an opinionated set of defaults. My opinion is that most apps shouldn't be using sagas unless they have a clearly demonstrated need for truly complex workflows, and that thunks are the right choice as a default option.
Finally, there is already an open issue to discuss what a more "declarative" side effect approach in RTK might look like. We'll see if anything actually comes out of that discussion.
This is a post in the Blogged Answers series. Other posts in this series:
- Aug 02, 2017 - Blogged Answers: Webpack HMR vs React-Hot-Loader
- Dec 18, 2017 - Blogged Answers: Resources for Learning React
- Dec 18, 2017 - Blogged Answers: Resources for Learning Redux
- Mar 29, 2018 - Blogged Answers: Redux - Not Dead Yet!
- Jan 19, 2019 - Blogged Answers: Debugging Tips
- Jul 10, 2019 - Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
- Nov 26, 2019 - Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer
- Jan 01, 2020 - Blogged Answers: A Comparison of Redux Batching Techniques
- Jan 01, 2020 - Blogged Answers: Reasons to Use Thunks
- Jan 01, 2020 - Blogged Answers: Years in Review, 2018-2019
- Jan 19, 2020 - Blogged Answers: React, Redux, and Context Behavior
- Feb 22, 2020 - Blogged Answers: Coder vs Tech Lead - Balancing Roles
- Feb 22, 2020 - Blogged Answers: Why Redux Toolkit Uses Thunks for Async Logic
- May 12, 2020 - Blogged Answers: Why I Write
- May 17, 2020 - Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
- Jun 21, 2020 - Blogged Answers: React Components, Reusability, and Abstraction