Blogged Answers: Why Redux Toolkit Uses Thunks for Async Logic
This is a post in the Blogged Answers series.
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 middleware
and enhancer
arguments, allowing you to add whatever async middleware you want as part of your store setup, same as the base createStore
.
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
yield
keyword does - Make sure you call
sagaMiddleware.run(rootSaga)
- 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
call()
,put()
, andfork()
- 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 08, 2023 - Blogged Answers: My Experience Modernizing Packages to ESM
- Jul 06, 2022 - Blogged Answers: How I Estimate NPM Package Market Share (and how Redux usage compares to other libraries)
- Jun 22, 2021 - Blogged Answers: The Evolution of Redux Testing Approaches
- Jan 18, 2021 - Blogged Answers: Why React Context is Not a "State Management" Tool (and Why It Doesn't Replace Redux)
- Jun 21, 2020 - Blogged Answers: React Components, Reusability, and Abstraction
- May 17, 2020 - Blogged Answers: A (Mostly) Complete Guide to React Rendering Behavior
- May 12, 2020 - Blogged Answers: Why I Write
- Feb 22, 2020 - Blogged Answers: Why Redux Toolkit Uses Thunks for Async Logic
- Feb 22, 2020 - Blogged Answers: Coder vs Tech Lead - Balancing Roles
- Jan 19, 2020 - Blogged Answers: React, Redux, and Context Behavior
- Jan 01, 2020 - Blogged Answers: Years in Review, 2018-2019
- Jan 01, 2020 - Blogged Answers: Reasons to Use Thunks
- Jan 01, 2020 - Blogged Answers: A Comparison of Redux Batching Techniques
- Nov 26, 2019 - Blogged Answers: Learning and Using TypeScript as an App Dev and a Library Maintainer
- Jul 10, 2019 - Blogged Answers: Thoughts on React Hooks, Redux, and Separation of Concerns
- Jan 19, 2019 - Blogged Answers: Debugging Tips
- Mar 29, 2018 - Blogged Answers: Redux - Not Dead Yet!
- Dec 18, 2017 - Blogged Answers: Resources for Learning Redux
- Dec 18, 2017 - Blogged Answers: Resources for Learning React
- Aug 02, 2017 - Blogged Answers: Webpack HMR vs React-Hot-Loader
- Sep 14, 2016 - How I Got Here: My Journey Into the World of Redux and Open Source