Redux toolkit

dbillinghamuk
4 min readAug 5, 2021

“Modern Redux” Learning the new features

Introduction

I have no strong opinions when it comes to; redux / flux / state management patterns in general. However I like to see whats out there, and have found redux quite useful with data heavy apps in the past. I personally find a repo easier/quicker to pick up when a common state management pattern has been used, and I like the fact the state is immutable, and time travel provides a nice dev experience.

Recently I was quite surprised to discover how much redux had moved on, I herd/read the term “Modern Redux” bounded around, not sure if thats officially what using these new functions/patterns in an app is now called, but regardless these are my learnings.

createApi

This function is used to make API calls, its part of react toolkit and called RTK query. It accepts an object which has a number of properties to construct the API request. Whats nice is that it automatically creates react hooks based on the endpoints.

// /features/example/apiSlice.tsimport { 
createApi,
fetchBaseQuery
} from '@reduxjs/toolkit/query/react';
import { Example } from './types';
export const apiSlice = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({
baseUrl: `/api`
}),
endpoints: (builder) => ({
fetchExamples: builder.query<Example[], void>({
query: () => '/example'
}),
fetchCardsError: builder.query<Example[], void>({
query: () => '/example-error-route'
})
})
});
export const {
useFetchExamplesQuery,
useFetchExamplesErrorQuery
} = apiSlice;

createSlice

Slices is the term given to create your reducers and actions in one step. It accepts and name and an initialState as you would expect.

Within the reducers property you list your reducers which get exported within slice.reducer to be hooked up to the root reducer. The reducers use a package called Immer which allow you to write, what looks like mutating code, but actually gets run as immutable code.

There is also a property called extraReducers which allows you to hook into other action types from other slices and update the state. Using their builder pattern provides a number of functions to match on these action types.

import { createSlice } from '@reduxjs/toolkit';
import { apiSlice } from './apiSlice';
import { ExampleState, Example } from './types';
import { shuffle } from './utils'; //simple shuffle function
const initialState: ExampleState = {
examples: []
};
const exampleSlice = createSlice({
name: 'example',
initialState,
reducers: {
shuffled(state) {
shuffle(state.cards);
}
},
extraReducers: (builder) => {
builder
.addMatcher(
apiSlice.endpoints.fetchExamples.matchFulfilled,
(state, action) => {
state.cards = action.payload;
}
)
.addDefaultCase(() => {});
}
});
export const { shuffled } = exampleSlice.actions;
export const exampleReducer = exampleSlice.reducer;

Reducers

With both slices we need to plug them into the root reducer as below. Redux toolkits configureStore does a number of things including:

  • turning on dev tools extension
  • adding thunk middleware
  • adding dev checks, like accidental mutations
  • auto combine reducers
// /app/store.ts;import { configureStore } from '@reduxjs/toolkit';
import { exampleReducer } from './features/example/exampleSlice';
import { apiSlice } from './features/example/apiSlice';
export const store = configureStore({
reducer: {
example: exampleReducer,
[apiSlice.reducerPath]: apiSlice.reducer
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(apiSlice.middleware)
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;

Provider

Wrap the app in a react redux provider passing in the store.

// _app.tsximport { Provider } from 'react-redux';
import { store } from './app/store';
const MyApp = ({ Component}) => (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);

Hooks

Adding a hooks file here with the following settings will export useAppDispatch and useAppSelector which will be typescript typed when being consumed.

// /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;

Consume the slices

To consume these hooks/slices we can import and call in the component shown below.

Notice that the api hooks have a number of properties which can be used to make a nicer user experience. Along with the response data, the hook also exports if the call is in a fetching state with isFetching and also if the call has returned an error with isError.

The useAppSelector takes a callback which returns a typed state object to allow you to select which piece of data off the store you require. The useAppDispatch hook can be initialised and passed an automatically generated action type function returned from the slice, which can be called in an event handler.

// index.tsximport { 
useAppDispatch,
useAppSelector
} from '../app/hooks';
import {
useFetchExamplesQuery,
useFetchExamplesErrorQuery
} from './features/example/apiSlice';
import { shuffled } from './features/example/exampleSlice';
export const App = () => {
const { isFetching, isError} = useFetchCardsQuery();
const { isError: isIntendedError } = useFetchCardsErrorQuery();
const examples = useAppSelector((state) =>
state.example.examples);
const dispatch = useAppDispatch();
const clickHandler = () => {
dispatch(shuffled());
};

return (
{ isError && <div>Error occurred</div> }
{ isFetching && <div>loading...</div> } <button type="button" onClick={clickHandler}>
Shuffle examples
</button>
{ examples.map(eg => <div key={eg.id}>{eg.name}</div>)) } { isIntendedError && <div>Intended error occurred</div> }
)}

Conclusion

Whether a fan of redux or hand rolling state changes with react context and useReducer hooks, the changes to how I use “Classic Redux” in applications is vast in comparison. I think moving forward in state heavy applications “Modern Redux” must be a strong contender for state management and data fetching.

--

--

dbillinghamuk

Software dev — Javascript, node, express, mongo, react, redux, rxjs, es6, ramda