1

I am reading this page getting into react-redux https://redux.js.org/introduction/getting-started

I am very confused looking at the Basic Example which has 12 lines of code(excluding usage, imports, and comments)

Then I read this line on the "Redux Toolkit Example" which below the code states "Redux Toolkit allows us to write shorter logic that's easier to read, while still following the same Redux behavior and data flow." However, this example is 19 lines of code(excluding usage, imports, and comments)

The usage in both examples is 3 lines of code. Could someone explain this to me?

Perhaps when it scales, the redux toolkit example does save more code? Honestly, I find the Basic Example MUCH easier to read and maintain. NOTE: I am a complete newb which leads me to believe the basic example may be better as we ramp up and hire developers. This allows them to ramp up more quickly(but I am only a single data point amongst newbs)

thanks, Dean

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
Dean Hiller
  • 19,235
  • 25
  • 129
  • 212

1 Answers1

5

You're right about the lines of code in the example. Perhaps that simple counter example doesn't do justice to how much code Redux Toolkit can save because they aren't adding all the "bells and whistles" in their non-toolkit version.

This section is called "getting started with Redux" rather than "migrating to Redux Toolkit" so I suspect they don't want to overwhelm the user by introducing best practices like action creator functions which aren't strictly necessary. But you're not seeing the "write less code" benefit because most of the code that you can remove with the Toolkit is coming from things that weren't in the example in first place.

Action Creators

One of the main benefits of the createSlice function is that it automatically creates the action name constants and action creator functions to go along with each case in the reducer.

This example is just dispatching raw actions directly with string names store.dispatch({ type: 'counter/incremented' }). Most devs would not do this because of how fragile and inflexible it is.

An alternate version of the non-toolkit example, what you would see in most code, looks more like this:

// action name constants
const INCREMENTED = 'counter/incremented';
const DECREMENTED = 'counter/decremented';

// action creator functions
// usually most take some arguments
const incremented = () => ({
  type: INCREMENTED,
})

const decremented = () => ({
  type: DECREMENTED,
})


// reducer function
function counterReducer(state = { value: 0 }, action) {
  switch (action.type) {
    case INCREMENTED:
      return { value: state.value + 1 }
    case DECREMENTED:
      return { value: state.value - 1 }
    default:
      return state
  }
}

If you want to include typescript types it gets even worse.

Immutability

The reducer itself could get really lengthy if you are trying to do immutable updates on deeply nested data.

Here's an example copied from those docs on how to safely update the property state.first.second[someId].fourth

Without Toolkit

function updateVeryNestedField(state, action) {
  return {
    ...state,
    first: {
      ...state.first,
      second: {
        ...state.first.second,
        [action.someId]: {
          ...state.first.second[action.someId],
          fourth: action.someValue
        }
      }
    }
  }
}

With Toolkit:

const reducer = createReducer(initialState, {
  UPDATE_ITEM: (state, action) => {
    state.first.second[action.someId].fourth = action.someValue
  }
})

configureStore

The Toolkit configureStore actually does save a step vs the Redux createStore function when you are combining more than one reducer. But again this example fails to show it. Instead the Toolkit version is longer because we set a reducer property rather than just passing the reducer.

A typical Redux app uses the combineReducers utility to combine multiple reducers as properties on an object:

import {createStore, combineReducers} from "redux";

const rootReducer = combineReducers({
  counter: counterReducer,
  other: otherReducer
});

const vanillaStore = createStore(rootReducer);

With the Toolkit you can just pass your reducers map directly without calling combineReducers.

import {configureStore} from "@reduxjs/toolkit";

const toolkitStore = configureStore({
  reducer: {
    counter: counterReducer,
    other: otherReducer
  }
});

Which is roughly the same amount of code. But it also includes some default middleware which would be extra lines in the non-toolkit example.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • 1
    Yep, well said. In addition, the ["Modern Redux with Redux Toolkit" page in the "Redux Fundamentals" tutorial](https://redux.js.org/tutorials/fundamentals/part-8-modern-redux) shows more realistic examples of how RTK simplifies actual Redux logic (and even there, it's working off a "todo app" example that's pretty minimal). – markerikson Mar 22 '21 at 16:04
  • @Linda Great post. Quick question. I assume the reducer does a deep copy so you are just modifying the copy at that point of ALL the fields I want to change. Is that correct? (both pieces, deep copy and I can modify more than one field) – Dean Hiller Mar 23 '21 at 13:42
  • You can modify more than one field — correct. It creates a deep copy — not quite. Deep copies are bad for performance as it will cause re-tending of components whose data didn’t actually change. The toolkit uses [Immer](https://immerjs.github.io/immer/) which uses a Proxy to create a draft state that you can modify. The state variable in your reducer cases is a Proxy of the actual state. – Linda Paiste Mar 23 '21 at 15:42