DEV Community

Cover image for The Redux Toolkit Pattern
👺Mervyn
👺Mervyn

Posted on

The Redux Toolkit Pattern

Redux Toolkit has been around for a while now. It's still the same state manager we love with a kinda new pattern but still familiar and less overhead a.k.a it's "baggage"!

Since it's 2021, RTL and typescript are perfect together. It would be a crime not to use it together.

We Begin!

Start with installing Redux and RTL to React.
npm install @reduxjs/toolkit react-redux

The Pattern

As it always is, we start with our store file.
Create the file inside app folder, which path will look like src/app/store.ts. Then lets import configureStore

import { configureStore } from '@reduxjs/toolkit'

// export the store so we can pass it to our Provider wrapper
export const store = configureStore({
  reducer: {},
})

// Notice that we are exporting inferred typing to be used later for some cool type hintings 
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
Enter fullscreen mode Exit fullscreen mode

Now, don't forget to wrap your whole app with Redux Provider

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import { store } from './app/store'
import { Provider } from 'react-redux'

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)
Enter fullscreen mode Exit fullscreen mode

Hooks

Before we move on, remember the types we exported from the store earlier? We are going to use that to type charge the hooks for dispatch and selector

src/app/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import { AppDispatch, RootState } from './store'

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

Enter fullscreen mode Exit fullscreen mode

With these you'll get typing on what ever you put into your reducer and initial state when using the Redux hooks

Slice

Slice is just where your reducer and action is going to be if your still thinking of classic Redux pattern.

The recommended file structure should follow this file structure pattern features/counter/counterSlice.ts

import { createSlice, PayloadAction } from '@reduxjs/toolkit'

export interface CounterState {
  value: number
}

const initialState: CounterState = {
  value: 0,
}

export const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers. It
      // doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload
    },
  },
})

// Action creators are generated for each case reducer function
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer
Enter fullscreen mode Exit fullscreen mode

Then we add the slice to our store
app/store.ts

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
})
Enter fullscreen mode Exit fullscreen mode

Usage

import React from 'react'
import { useAppSelector, useAppDispatch } from '../../app/hooks'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useAppSelector((state) => state.counter.value)
  const dispatch = useAppDispatch()

  return (
    <div>
      <div>
        <button
          aria-label="Increment value"
          onClick={() => dispatch(increment())}
        >
          Increment
        </button>
        <span>{count}</span>
        <button
          aria-label="Decrement value"
          onClick={() => dispatch(decrement())}
        >
          Decrement
        </button>
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

Alt Text

Discussion (0)