Redux Toolkit (RTK) has revolutionized state management in React applications by reducing boilerplate and providing sensible defaults. While basic usage is straightforward, mastering advanced patterns can elevate your state management to production-grade quality. Hereβs a deep dive into professional techniques used by top React developers.
1. Dynamic Reducer Injection
Problem:
Loading all reducers upfront hurts performance in large apps
Solution:
// store.js
import { configureStore } from '@reduxjs/toolkit'
import { createReducerManager } from './reducerManager'
const staticReducers = {
users: usersReducer,
auth: authReducer
}
export function setupStore(initialState) {
const reducerManager = createReducerManager(staticReducers)
const store = configureStore({
reducer: reducerManager.reduce,
preloadedState: initialState
})
store.reducerManager = reducerManager
return store
}
// reducerManager.js
export function createReducerManager(initialReducers) {
const reducers = { ...initialReducers }
let combinedReducer = combineReducers(reducers)
let keysToRemove = []
return {
reduce: (state, action) => {
if (keysToRemove.length > 0) {
state = { ...state }
keysToRemove.forEach(key => delete state[key])
keysToRemove = []
}
return combinedReducer(state, action)
},
add: (key, reducer) => {
reducers[key] = reducer
combinedReducer = combineReducers(reducers)
},
remove: key => {
keysToRemove.push(key)
delete reducers[key]
}
}
}Usage in Components:
useEffect(() => {
store.reducerManager.add('dynamicFeature', dynamicReducer)
return () => {
store.reducerManager.remove('dynamicFeature')
}
}, [])2. Normalized State with Entity Adapter
Problem:
Nested/denormalized state causes performance issues
Solution:
import { createEntityAdapter } from '@reduxjs/toolkit'
const usersAdapter = createEntityAdapter({
selectId: user => user.id,
sortComparer: (a, b) => a.name.localeCompare(b.name)
})
const usersSlice = createSlice({
name: 'users',
initialState: usersAdapter.getInitialState(),
reducers: {
userAdded: usersAdapter.addOne,
userUpdated: usersAdapter.updateOne,
userRemoved: usersAdapter.removeOne,
usersReceived(state, action) {
usersAdapter.setAll(state, action.payload)
}
}
})
// Selectors
export const {
selectAll: selectAllUsers,
selectById: selectUserById,
selectIds: selectUserIds
} = usersAdapter.getSelectors(state => state.users)Benefits:
- Automatic memoization
- Optimized updates
- Built-in CRUD operations
3. Advanced Middleware Patterns
Action Sequencing
const sequenceMiddleware = storeAPI => next => action => {
if (action.meta?.sequenceId) {
const state = storeAPI.getState()
const lastSequence = state.lastSequenceId || 0
if (action.meta.sequenceId <= lastSequence) {
return // Ignore out-of-order actions
}
}
return next(action)
}Optimistic Updates with Rollback
import { createAsyncThunk, nanoid } from '@reduxjs/toolkit'
const updateUser = createAsyncThunk(
'users/update',
async (userData, { dispatch, getState }) => {
const transactionId = nanoid()
dispatch({
type: 'users/optimisticUpdate',
payload: { ...userData, pending: true },
meta: { transactionId }
})
try {
const response = await api.updateUser(userData)
return { ...response, transactionId }
} catch (error) {
return { error, transactionId }
}
},
{
condition: (_, { getState }) => {
const { isUpdating } = getState().users
return !isUpdating // Skip if already updating
}
}
)
// In extraReducers:
.addCase(updateUser.fulfilled, (state, action) => {
const { transactionId, ...user } = action.payload
usersAdapter.updateOne(state, {
id: user.id,
changes: { ...user, pending: false }
})
})
.addCase(updateUser.rejected, (state, action) => {
const { transactionId } = action.meta.arg
// Rollback logic
})4. Selector Memoization Patterns
Reselect with RTK
import { createSelector } from '@reduxjs/toolkit'
const selectUsers = state => state.users.entities
const selectActiveFilter = state => state.users.activeFilter
export const selectFilteredUsers = createSelector(
[selectUsers, selectActiveFilter],
(users, filter) => {
return users.filter(user =>
filter === 'all' || user.status === filter
)
}
)Dynamic Selector Factory
export const makeSelectUserById = () => createSelector( [selectUsers, (_, id) => id], (users, id) => users[id] ) // Usage: const selectUser = makeSelectUserById() const user = useSelector(state => selectUser(state, userId))
5. Server-State Synchronization with RTK Query
Advanced Cache Management
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['User', 'Post'],
endpoints: builder => ({
getUsers: builder.query({
query: () => 'users',
providesTags: ['User']
}),
updateUser: builder.mutation({
query: ({ id, ...patch }) => ({
url: `users/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: (result, error, { id }) => [
{ type: 'User', id },
'Post' // Also invalidate all posts
],
onQueryStarted: async (arg, { dispatch, queryFulfilled }) => {
// Optimistic update
const patchResult = dispatch(
api.util.updateQueryData('getUser', arg.id, draft => {
Object.assign(draft, arg)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo() // Rollback
}
}
})
})
})6. State Persistence Strategies
Rehydration with Redux-Persist
import { persistReducer, persistStore } from 'redux-persist'
import storage from 'redux-persist/lib/storage'
const persistConfig = {
key: 'root',
storage,
whitelist: ['auth'],
transforms: [encryptTransform] // For sensitive data
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export const store = configureStore({
reducer: persistedReducer,
middleware: getDefaultMiddleware =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
}
})
})
export const persistor = persistStore(store)Differential Persistence
const advancedPersistConfig = {
key: 'userSettings',
storage,
serialize: data => {
const { temporary, ...persistent } = data
return JSON.stringify(persistent)
},
deserialize: str => {
const persistent = JSON.parse(str)
return { ...persistent, temporary: {} }
}
}7. Testing Strategies
Middleware Testing
test('auth middleware processes token', () => {
const store = mockStore({})
const next = jest.fn()
const action = { type: 'LOGIN', payload: { token: 'abc123' } }
authMiddleware(store)(next)(action)
expect(localStorage.setItem).toHaveBeenCalledWith('token', 'abc123')
expect(next).toHaveBeenCalledWith(action)
})Reducer Composition Testing
describe('nested reducers', () => {
let store
beforeEach(() => {
store = setupStore()
store.reducerManager.add('dynamicFeature', dynamicReducer)
})
test('handles cross-slice actions', () => {
store.dispatch({ type: 'TRIGGER_UPDATE' })
expect(store.getState().dynamicFeature).toEqual(/* expected */)
})
})Key Takeaways
- Dynamic injection enables code splitting for reducers
- Entity adapter solves normalization challenges
- Middleware patterns handle complex side effects
- Advanced selectors optimize performance
- RTK Query simplifies server state management
- Persistence strategies balance UX and security
- Testing techniques ensure reliability
These patterns represent professional-grade Redux architecture used in large-scale applications. Implement them progressively as your applicationβs complexity grows.
Do you want to know how to develop your skillset to become a Java Rockstar?
Subscribe to our newsletter to start Rocking right now!
To get you started we give you our best selling eBooks for FREE!
1. JPA Mini Book
2. JVM Troubleshooting Guide
3. JUnit Tutorial for Unit Testing
4. Java Annotations Tutorial
5. Java Interview Questions
6. Spring Interview Questions
7. Android UI Design
and many more ....
I agree to the Terms and Privacy Policy
Thank you!
We will contact you soon.
π Photo of Eleftheria Drosopoulou
Eleftheria DrosopoulouJune 20th, 2025Last Updated: June 13th, 2025
Eleftheria DrosopoulouJune 20th, 2025Last Updated: June 13th, 2025
0 1,110 4 minutes read

This site uses Akismet to reduce spam. Learn how your comment data is processed.