Belajar Redux - RTK Query Advanced
Episode 17 of 23

Belajar Redux - RTK Query Advanced

Episode ini mendalami fitur lanjutan RTK Query: pagination dan infinite query, GraphQL dengan graphql-request, streaming updates, prefetch berprioritas, codegen dari OpenAPI, serta custom baseQuery untuk multi-API dan otomatis auth refresh saat token kedaluwarsa.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Episode 8 dan 9 memberi fondasi RTK Query: query, mutation, dan invalidation. Aplikasi nyata menuntut lebih: daftar tak berujung, data real-time, GraphQL, dan API client yang diturunkan dari spesifikasi. Episode 17 membahas fitur lanjutan tersebut satu per satu.

Kita akan memakai pagination dan infinite query, berkomunikasi dengan server GraphQL, menerima streaming updates, mengatur prioritas prefetch, meng-generate endpoint dari OpenAPI, serta menulis custom baseQuery yang menangani refresh token secara otomatis.

Pagination dan Infinite Query

Pagination Manual dengan Page State

Cara paling umum: simpan nomor halaman di state lokal, dan lewatkan sebagai argumen query. Cache RTK Query akan memisahkan data per halaman secara otomatis:

JSPagination dengan useLazyGetPostsQuery
export function PaginatedList() {
  const [page, setPage] = useState(1)
  const [fetchPosts, { data, isFetching }] = useLazyGetPostsQuery()
 
  return (
    <>
      <button onClick={() => fetchPosts(page)}>Muat halaman {page}</button>
      <button disabled={isFetching} onClick={() => setPage((p) => p + 1)}>
        Halaman berikutnya
      </button>
    </>
  )
}

useLazyGetPostsQuery memberi kontrol penuh kapan request dijalankan. Argumen page menjadi kunci cache, sehingga kembali ke halaman 2 tidak mengirim request ulang — datanya masih tersimpan.

Infinite Scroll dengan createApi

Untuk daftar tak berujung, buat endpoint yang menerima cursor dan tetap memakai cache sebagai akumulator:

JSEndpoint paginated dengan cursor
getPostsPaginated: builder.query<PaginatedPosts, number>({
  query: (page) => `posts?page=${page}&limit=10`,
  serializeQueryArgs: ({ endpointName }) => endpointName,
  merge: (currentCache, newItems) => {
    currentCache.items.push(...newItems.items)
    currentCache.nextPage = newItems.nextPage
  },
  forceRefetch: ({ currentArg, previousArg }) => currentArg !== previousArg,
}),

merge menambahkan halaman baru ke cache yang sama, dan forceRefetch memastikan halaman diambil ulang saat argumen berubah. Kombinasi keduanya menghasilkan infinite scroll tanpa kode manajemen daftar manual.

GraphQL dan Streaming Updates

GraphQL dengan graphql-request

RTK Query tidak membatasi protokol. Untuk GraphQL, tulis baseQuery custom yang memakai graphql-request:

Install client GraphQL
npm install graphql-request graphql
JSGraphQL baseQuery
import { GraphQLClient } from "graphql-request"
 
const gqlClient = new GraphQLClient("https://api.example.com/graphql", {
  headers: { authorization: "Bearer " + getAccessToken() },
})
 
export const graphqlBaseQuery = async ({ document, variables }) => {
  try {
    const data = await gqlClient.request(document, variables)
    return { data }
  } catch (error) {
    return { error }
  }
}

baseQuery hanya perlu mengembalikan { data } atau { error }. GraphQL ditangani sepenuhnya oleh graphql-request, sementara caching dan lifecycle tetap ditangani RTK Query.

Streaming Updates

Untuk update real-time, misalnya harga saham atau notifikasi, kombinasikan subscription dengan cache update via updateQueryData:

JSStreaming ke cache RTK Query
export const pricesApi = createApi({
  reducerPath: "pricesApi",
  baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
  endpoints: (builder) => ({
    getPrices: builder.query<Price[], void>({
      query: () => "prices",
    }),
  }),
})
 
export const onPriceTick = (price: Price) => {
  pricesApi.util.updateQueryData("getPrices", undefined, (draft) => {
    const idx = draft.findIndex((p) => p.symbol === price.symbol)
    if (idx !== -1) draft[idx] = price
  })
}

Panggil onPriceTick dari koneksi WebSocket. Data langsung memutasi cache tanpa request ulang — UI merender harga terbaru secara otomatis.

Prefetch dengan Prioritas

Memuat Data Sebelum Navigasi

RTK Query menyediakan prefetch yang mengirim request lebih awal. Prioritas diatur lewat argumen kedua: force untuk selalu mengambil ulang, ifOlderThan untuk melewatkan cache yang masih segar:

JSPrefetch berprioritas
export const prefetchNextPage = () => {
  postsApi.util.prefetch("getPostsPaginated", 3, { ifOlderThan: 30000 })
}

ifOlderThan: 30000 artinya request hanya dikirim bila cache sudah lebih lama dari 30 detik. Ini cara sederhana menyiapkan data halaman berikutnya saat user mengarahkan mouse ke tombol next.

Codegen dan Custom baseQuery

Generate Endpoint dari OpenAPI

@rtk-query/codegen-openapi menghasilkan seluruh endpoint dari spesifikasi OpenAPI — mengurangi pekerjaan menulis query manual dan menjaga tipe tetap sinkron dengan API:

Install dan jalankan codegen
npm install -D @rtk-query/codegen-openapi
npx @rtk-query/codegen-openapi openapi-config.ts

File config openapi-config.ts menyebutkan file spesifikasi, lokasi output, dan nama api client. Hasilnya: file TypeScript berisi createApi, semua endpoint, dan tipe data — siap dipakai langsung.

Custom baseQuery untuk Auth Refresh

baseQuery juga tempat yang tepat untuk menangani 401. Tangkap error, refresh token, lalu retry request asli:

JSbaseQuery dengan refresh token
import { fetchBaseQuery, type BaseQueryFn } from "@reduxjs/toolkit/query/react"
 
const rawBase = fetchBaseQuery({ baseUrl: "/api" })
 
export const baseQueryWithAuth: BaseQueryFn<
  string | FetchArgs,
  unknown,
  FetchBaseQueryError
> = async (args, api, extraOptions) => {
  let result = await rawBase(args, api, extraOptions)
  if (result.error?.status === 401) {
    const refresh = await rawBase("/auth/refresh", api, extraOptions)
    if (refresh.data) {
      setAccessToken(refresh.data.accessToken)
      result = await rawBase(args, api, extraOptions)
    }
  }
  return result
}

Saat token kedaluwarsa, middleware menjalankan refresh sekali lalu mengulang request awal. Seluruh endpoint yang memakai baseQuery ini otomatis mendapat perilaku sama — tanpa menulis retry di tiap hook.

Tip

Gabungkan custom baseQuery dengan fetchBaseQuery bawaan bila memungkinkan: ia sudah menangani headers, query params, form data, dan error normalisasi. Hanya lapisi dengan logika auth bila benar-benar dibutuhkan.

Penutup

RTK Query matang untuk skenario dunia nyata: pagination dan infinite scroll via merge serta forceRefetch, GraphQL lewat baseQuery custom, streaming dengan updateQueryData, prefetch berprioritas, endpoint yang di-generate dari OpenAPI, dan refresh token terpusat. Fitur-fitur ini menjadikan RTK Query lapisan data yang hampir tidak perlu ditulis manual.

Inti yang harus dibawa pulang:

  • Pagination memakai argumen sebagai kunci cache; infinite scroll dikelola merge dan forceRefetch.
  • GraphQL memakai custom baseQuery berbasis graphql-request.
  • Streaming updates memakai util.updateQueryData terhadap cache yang ada.
  • prefetch dengan ifOlderThan mengatur prioritas pengambilan data.
  • @rtk-query/codegen-openapi meng-generate endpoint dan tipe dari OpenAPI.
  • Custom baseQuery bisa menangani multi-API dan refresh token secara otomatis.

Episode 18 berikutnya membahas testing — kalian akan menguji reducer secara murni, menguji async thunk dengan mock fetch, merender komponen dengan store uji memakai testing-library, dan mengintegrasikan MSW untuk mem-mock API secara realistis.

Belajar Redux - RTK Query Advanced | Belajar Redux