DEV Community

Discussion on: Redux is Dead: Long Live Redux Toolkit

Collapse
 
markerikson profile image
Mark Erikson • Edited

Which "opinions" are you concerned about?

The only real complaint we've gotten is actually that RTK's use of Immer is not configurable, and that's specifically because Immer makes your reducer logic much simpler and eliminates accidental mutations, which have always been the #1 type of bug in Redux apps. So, that particular "opinion" is non-negotiable. Other than that, all that RTK does is take common patterns that everyone was already doing anyway, and simplifies them.

Beyond that, RTK is already written in TypeScript, and we've designed its API to minimize the amount of types you have to declare. Example:

// Define a type for the slice state
interface CounterState {
  value: number
}

// Define the initial state using that type
const initialState: CounterState = {
  value: 0
}

export const counterSlice = createSlice({
  name: 'counter',
  // `createSlice` will infer the state type from the `initialState` argument
  initialState,
  reducers: {
    increment: state => {
      state.value += 1
    },
    decrement: state => {
      state.value -= 1
    },
    // Use the PayloadAction type to declare the contents of `action.payload`
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

All you need to declare is the type for this slice's state, and the payload type for each action, and RTK will infer the rest for you. See redux.js.org/tutorials/typescript-... for a summary of recommended TS setup.

Collapse
 
srmagura profile image
Sam Magura

It seems like I may be in the minority here, which is fine 😄. I also don't want to detract from your work on the library!

I suppose the three parts of RTK that I consider very opinionated are:

  • createAsyncThunk — this seems to be specifying a side effect model for Redux. I've only ever used Redux Saga for this so I don't really understand thunks. Maybe if I used them I would understand why this was included in RTK.
  • createEntityAdapter — I always thought the Redux philosophy was to design action types, reducers, and selectors specific to each entity since different entities usually have different operations and filtering logic. I can see createEntityAdapter being useful if you have a ton of very straightforward CRUD in your app.
  • RTK Query — I think Redux Toolkit should focus on helping you build actions, reducers, and selectors (core Redux features) without going too far beyond that. RTK Query is a library for querying & caching server state, neither of which are features of Redux (even if many applications use Redux like this). My initial reaction to RTK Query would have been more positive if it was a separate package.

Just my two cents. I know these are all optional features and that tree shaking will probably remove them if you don't use them.

Thread Thread
 
markerikson profile image
Mark Erikson

Responding to each of those items:

createAsyncThunk

Yes, we do specifically recommend thunks as the default approach for side effects in Redux. That does not mean it's the only approach you can use. All the existing side effects libs (sagas, observables, 5 million others) still work fine. But the most common side-effect-y thing people need to do is basic AJAX fetching. Thunks are the simplest approach to doing that, and they have also always been the most commonly used approach.

FWIW, we're actually trying to nudge people away from using sagas as a default approach, especially for just data fetching. Don't get me wrong, sagas are a great power tool for complex async workflows that involve debouncing or "background thread"-type behavior. But they're overkill for basic data fetching, and one of the contributing reasons why people have long complained about the "boilerplate" associated with Redux. They shouldn't be the first tool people reach for when they want to fetch data, and they shouldn't be something that's "required to use with Redux".

So, since people were already doing this anyway, and the docs have always taught this "promise lifecycle actions" pattern from the beginning, it made sense to add createAsyncThunk to simplify the code that people were already writing.

References:

createEntityAdapter

We have always recommend storing items in a normalized lookup table. In fact, I specifically wrote the Structuring Reducers: Normalizing State Shape docs page back in 2016 to officially start teaching that concept. But, we never had any built-in APIs that helped with any of that process. For any collection of items, you are very likely to do a number of CRUD-y type operations.

So, createEntityAdapter provides the reducers that implement those operations, and it's up to you to decide when and how it makes sense to run those reducers, and in response to what actions. They can be used as complete "case reducers", or as "mutating helper functions" inside of other Immer-powered reducers. Again, we just looked at "what are things people commonly have to write on their own?", and provided some generic implementations. You're free to use them, or not. All these APIs are pick-and-choose.

RTK Query

Aas mentioned with thunks, users have always used Redux stores as a server state cache. It's just that Redux never included anything built in to help with that process. So, you inevitably ended up having to write all your own logic to manage loading state, make the requests, define the actions, dispatch the actions, save the data, handle errors, etc. The growth of React Query and Apollo have shown that the community has become much more interested in "server state caching" than "state management" in many cases. Redux makes a perfectly fine place to cache data, but writing the code to do that yourself is a pain.

So, once again, we built a set of APIs that just simplifies what everyone else has been doing by hand already, and provides a best-in-class API for dealing with server state caching.

RTK Query is built on top of createAsyncThunk and the other APIs in Redux Toolkit. As the RTK tagline says, it's "The official, opinionated, batteries-included toolset for efficient Redux development". Yes, RTK Query is effectively a second library, but it it's also very reasonable to include it in the actual RTK package. For one thing, some enterprises have difficult approval processes to go through to add additional libraries. By including the RTK Query APIs directly in the RTK package, you get them "for free" just by bumping to RTK 1.6+.

And yes, not only do the RTK APIs all tree-shake so you only pay for what you use, RTK Query itself is an entirely separate entry point, so none of it gets included in the app unless you specifically import it.

As you can see, all of these APIs have a common theme:

  • These are things people were always doing with Redux, and have been taught in our docs
  • Because Redux didn't include anything built-in for this purpose, people were having to write this code by hand, or create their own abstractions
  • So we created standardized implementations of all these concepts that people could choose to use if they want to, so that people don't have to write any of this code by hand in the future.

Hopefully that helps clarify why these APIs exist :)

Thread Thread
 
srmagura profile image
Sam Magura

Awesome response Mark. That really clarifies why those APIs were included.

You've really got me thinking about this and I'm planning to write a post about my thoughts on RTK, both the pros and the cons. I will link it here when/if it gets completed.

Thread Thread
 
markerikson profile image
Mark Erikson

Sure, please do! I'd also suggest reading through my Redux Toolkit 1.0 post, which goes through the entire history and background of why RTK was created, what problems it tries to solve, the "vision" for the library, and the evolution of its development and APIs up to the 1.0 release.

Obviously I'm biased, but the only meaningful tradeoffs I personally see in using RTK over "vanilla" Redux are:

  • Byte size for the extra functions and dependencies
  • Immer is an abstraction layer, and it's possible for someone to look at some Immer-powered reducers and not realize that there's "magic" inside and that they still have to do updates immutably in the end
  • Because Immer wraps the draft state in a Proxy, debugging can be harder because logging or inspecting the draft shows the Proxy instead and the contents are very hard to read

On the flip side of those:

So yeah, RTK certainly isn't perfect, but I can only think of a couple very minor concerns, and those are in turn far outweighed by all the benefits: standardized APIs, smaller app code, and prevention of common mistakes.

Thread Thread
 
srmagura profile image
Sam Magura

My post is here.

I hope I succeeded in providing an accurate and balanced review of RTK. At the end of the day, it's all just my opinion.