Episode ini membahas extensibility Redux: struktur custom middleware, menulis middleware untuk logging, tracking, dan telemetri, lalu memperluas store dengan enhancers dan komposisi middleware default RTK agar ekstensi bekerja bersama createAsyncThunk dan RTK Query.

Sampai episode 12 kita memakai middleware yang disediakan RTK. Namun aplikasi produksi sering butuh perilaku khusus: mencatat setiap action, mengirim telemetri ke analytics, atau berinteraksi dengan library luar. Episode 16 membuka kotak hitam tersebut — kalian akan belajar menulis custom middleware dan memahami enhancers untuk memperluas kemampuan store.
Middlewares dan enhancers adalah dua lapisan berbeda. Middleware mencegat action sebelum mencapai reducer. Enhancers membungkus store itu sendiri, memungkinkan mengganti cara dispatch, getState, dan subscribe bekerja. Keduanya bisa dikombinasikan dengan aman bersama middleware default RTK.
Middleware berbentuk fungsi bersarang tiga level: (storeAPI) => (next) => (action). storeAPI berisi dispatch dan getState, next meneruskan action ke middleware berikutnya, dan lapisan terdalam yang dieksekusi per action:
import type { Middleware } from "@reduxjs/toolkit"
export const loggerMiddleware: Middleware = (storeAPI) => (next) => (action) => {
const prevState = storeAPI.getState()
const result = next(action)
const nextState = storeAPI.getState()
console.log("Action:", action.type, { prevState, nextState })
return result
}Urutan eksekusi penting: panggil next(action) lebih dulu bila middleware ingin mengamati state setelah reducer berjalan, atau tangani action sendiri dan jangan panggil next bila ingin mencegat sepenuhnya. next bukan dispatch — memanggil dispatch dari dalam middleware bisa memicu infinite loop.
Telemetri biasanya berjalan tanpa memblokir reducer: catat event, lalu teruskan action. Tambahkan properti meta agar data tidak mencemari state:
import type { Middleware } from "@reduxjs/toolkit"
const TRACKED = ["order/created", "checkout/completed", "auth/loginSucceeded"]
export const telemetryMiddleware: Middleware = (storeAPI) => (next) => (action) => {
const result = next(action)
if (TRACKED.includes(action.type)) {
const state = storeAPI.getState()
navigator.sendBeacon?.("/api/telemetry", JSON.stringify({
type: action.type,
user: state.auth.user?.id,
ts: Date.now(),
}))
}
return result
}navigator.sendBeacon mengirim telemetri secara asinkron tanpa memblokir render. Middleware tetap fokus pada efek samping; data telemetri tidak pernah masuk ke store.
Selalu kombinasikan middleware kustom dengan default RTK — tanpa itu, createAsyncThunk dan RTK Query tidak akan berfungsi. getDefaultMiddleware menerima konfigurasi dan menghasilkan array:
import { configureStore } from "@reduxjs/toolkit"
import { loggerMiddleware } from "./middleware/logger"
import { telemetryMiddleware } from "./middleware/telemetry"
import { authSlice } from "../features/auth/authSlice"
export const store = configureStore({
reducer: {
auth: authSlice.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(
loggerMiddleware,
telemetryMiddleware,
),
}).concat menambahkan middleware setelah default. Bila middleware harus melihat action lebih dulu, gunakan .prepend. Urutan menentukan siapa yang menangkap action terlebih dahulu dalam rantai.
Beberapa middleware default bisa disesuaikan atau dimatikan. Contoh: mematikan serializable check untuk data yang memang tidak serializable:
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ["drag/dropMove"],
ignoredPaths: ["canvas.shapes"],
},
}),Sebagian besar kasus tidak perlu mematikan check ini. Kalaupun harus, gunakan ignoredActions dan ignoredPaths agar cakupan pengecualian sesempit mungkin — jangan set false begitu saja.
Enhancer adalah fungsi yang menerima fungsi pembuat store dan mengembalikan store yang lebih kaya. Middleware itu sendiri adalah contoh enhancer: RTK Query di-injeksi lewat enhancer applyMiddleware internal. Contoh sederhana enhancer untuk menambahkan metode ke store:
import type { StoreEnhancer } from "@reduxjs/toolkit"
export const versionedStore: StoreEnhancer =
(createStore) => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState)
return {
...store,
version: "2026.1",
}
}Enhancer membungkus store asli dan menambahkan properti. Pada praktiknya kalian jarang menulis enhancer sendiri — yang lebih umum adalah mengintegrasikan enhancer dari library eksternal yang menambah kemampuan store.
configureStore menerima enhancers sebagai fungsi yang bisa mengimbuhkan enhancer di sekitar default:
import { configureStore } from "@reduxjs/toolkit"
import { versionedStore } from "./enhancers/versionedStore"
export const store = configureStore({
reducer: {
auth: authSlice.reducer,
},
enhancers: (getDefaultEnhancers) =>
getDefaultEnhancers().concat(versionedStore),
})getDefaultEnhancers() mengembalikan enhancer bawaan seperti DevTools dan middleware. Menambah enhancer di akhir membuat wrapper terluar — cocok untuk logging store-level atau pembaruan eksternal.
Warning
Hati-hati dengan urutan: middleware yang di-dispatch dari dalam middleware lain, atau enhancer yang mengganti dispatch, bisa menimbulkan perilaku tak terduga. Uji setiap ekstensi dalam isolasi sebelum menggabungkannya.
Custom middleware dan enhancers memberi Redux extensibility tanpa batas. Middleware mencegat action untuk logging, tracking, dan telemetri tanpa menyentuh reducer; enhancers membungkus store untuk integrasi dengan library eksternal. Keduanya bekerja bersama middleware default RTK — asalkan kalian memahami urutan eksekusi dan tidak mengganggu alur next.
Inti yang harus dibawa pulang:
(storeAPI) => (next) => (action).next(action) untuk meneruskan action ke reducer; jangan panggil dispatch di dalamnya.getDefaultMiddleware().concat(...).ignoredActions dan ignoredPaths.dispatch.enhancers di configureStore.Episode 17 berikutnya memperdalam RTK Query advanced — kalian akan mengeksplorasi pagination dan infinite query, GraphQL dengan graphql-request, streaming updates, prefetch berprioritas, codegen dari OpenAPI, serta custom baseQuery untuk multi-API dan auth refresh.