Hướng dẫn sử dụng RTK Query trong Redux Toolkit

13 tháng 10, 2021 - 11700 lượt xem

RTK Query là một addon trong bộ thư viện Redux Toolkit. Nó giúp chúng ta thực hiện data fetching một cách đơn giản hơn thay vì sử dụng createAsyncThunk để thực hiện async action. Chú ý RTK Query là dùng để query (kết nối API), chứ không phải dùng để code async trong Redux thay cho createAsyncThunk.

Nếu bạn chưa từng sử dụng Redux Toolkit thì có thể xem bài hướng dẫn này trước: Hướng dẫn sử dụng React Router và Redux Toolkit.

Còn nếu chưa biết cách dùng createAsyncThunk thì xem bài hướng dẫn này: Hướng dẫn sử dụng createAsyncThunk trong Redux Toolkit.

Chúng ta sẽ tiếp tục sử dụng ví dụ này để demo: https://stackblitz.com/edit/react-router-redux-toolkit-fetch-api?file=src/App.js. Trong code mẫu này có sử dụng createAsyncThunk và fetch API để kết nối đến API. Chúng ta sẽ thay thế phần kết nối API bằng RTK Query.

Login form using react + redux + redux toolkit + rtk query

Đầu tiên chúng ta tạo 1 file mới, tương tự như tạo một slice, và file này sẽ dùng để khai báo các lệnh gọi API. Ví dụ trong thư mục store tạo thêm file api.js với nội dung như sau:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  // Tương tự tên Slice khi tạo Slice thông thường
  reducerPath: 'api',

  // Cấu hình chung cho tất cả request
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://fake-rest-api-nodejs.herokuapp.com/',
  }),

  // Các endpoints (lệnh gọi request)
  endpoints: (builder) => ({}),
});

Nhúng API này vào trong store như một Slice, sửa file store/index.js:

import { configureStore } from '@reduxjs/toolkit';
import { api } from './api';
import userReducer from './userSlice';

export const store = configureStore({
  reducer: {
    // Tạo thêm slice từ api
    [api.reducerPath]: api.reducer,

    // Slice thông thường
    user: userReducer,
  },

  // Thêm cấu hình middleware để dùng được các chức năng của RTK Query như caching, invalidation, polling, ...
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware),
});

Sau khi cấu hình xong, chúng ta có thể thêm các endpoint để thực hiện request đến API. Endpoint trong RTK Query phân làm 2 loại:

  • Query: Dùng để lấy dữ liệu (có thể lưu cache).
  • Mutation: Dùng để cập nhật dữ liệu (validate cache).

Với request login thì mình sẽ dùng loại mutation, sửa lại phần endpoints của file store/api.js:

export const api = createApi({
  ...
  endpoints: (builder) => ({
    // Tạo 1 request dạng mutation
    login: builder.mutation({
      query: (credentials) => ({
        url: `login`,
        method: 'POST',
        body: credentials,
      }),
    }),

    getUsers: builder.query({
      query: () => `users`,
    }),
  }),
  ...
}

// Export ra ngoài thành các hooks để sử dụng theo cú pháp use + endpoints (login) + endpoints type (mutation)
export const { useLoginMutation } = api;

Sửa lại trang login, sử dụng mutation ở trên để gọi API:

...
// Import hook để sử dụng
import { useLoginMutation } from '../store/api';

export default function Login() {
  // Sử dụng hook useLoginMutation sẽ trả về method login dùng để gọi request
  // kèm theo 1 số trạng thái như loading, dữ liệu hoặc lỗi trả về khi gọi request
  const [login, { isLoading, data, error }] = useLoginMutation();
  ...
  return (
    <>
      ...
      {/* Gọi login method lấy từ hook useLoginMutation() ở trên */}
      {/* Có thể sử dụng biến isLoading để hiển thị trạng thái loading thay cho state */}
      <button onClick={() => login({ email, password })} disabled={isLoading}>
        Login
      </button>
    </>
  );
}

Như vậy việc gọi API sẽ trở nên dễ dàng hơn. Bạn cũng có thể sử dụng biến error để hiển thị báo lỗi trên giao diện.

Trong trường hợp cần lưu dữ liệu vào trong store, ví dụ cập nhật state ở Slice khác thì làm tương tự như khi dùng createAsyncThunk. Sửa lại file store/userSlice.js để thêm logic lưu thông tin user sau khi user đăng nhập thành công:

  extraReducers: (builder) => {
    // Xử lý logic khi endpoint login được fulfilled
    builder.addMatcher(api.endpoints.login.matchFulfilled, (state, action) => {
      // Lưu thông tin user vào state
      state.currentUser = action.payload;
    });
  },

Hoặc RTK Query cũng hỗ trợ lấy state từ Slice khác. Ví dụ sau khi login thành công thì các request đến private API cần có gửi thêm token. Chúng ta có thể lấy token từ trong userSlice (state currentUser).

Sửa lại hàm fetchBaseQuery()store/api.js để cho phép các request đều gửi kèm token nếu có:

  baseQuery: fetchBaseQuery({
    baseUrl: 'https://fake-rest-api-nodejs.herokuapp.com/',

    // Xử lý header trước khi gửi request
    prepareHeaders: (headers, { getState }) => {
      // getState() giúp lấy ra toàn bộ state trong store
            // getState().user lấy ra state trong userSlice
      const token = getState().user.currentUser?.token;

      // Nếu có token thì thêm vào headers
      if (token) {
        headers.set('Authorization', `Bearer ${token}`);
      }

      return headers;
    },
  }),

Kiểm tra thử bằng cách tạo thêm 1 endpoint nữa để lấy ra danh sách user. Endpoint này là private và nếu không có token sẽ trả về lỗi 401. Bổ sung thêm endpoint getUsers vào file store/api.js:

export const api = createApi({
  ...
  endpoints: (builder) => ({
    login: builder.mutation(...),

    // Thêm endpoint dạng query
    getUsers: builder.query({
      query: () => `users`,
    }),
  }),
  ...
}

// Export các hooks ra ngoài để sử dụng
export const { useLoginMutation, useGetUsersQuery } = api;

Sửa lại nội dung trang Dashboard (sau khi login thành công) để hiển thị thông tin users lấy từ API:

...
import { useGetUsersQuery } from '../store/api';

export default function Dashboard() {
  // Sử dụng hook useGetUsersQuery để gọi API
  const { data, isLoading, error } = useGetUsersQuery();

  ...

  return (
    <>
      ...

      <h2>User List</h2>

      {isLoading ? (
        'fetching data ...'
      ) : (
        <ul>
          {data?.map((item) => (
            <li key={item.id}>
              {item.firstName} {item.lastName}
            </li>
          ))}
        </ul>
      )}
    </>
  );
}

Vậy là chúng ta đã làm xong một ứng dụng nhỏ sử dụng RTK Query cho việc fetching data. Addon này còn nhiều chức năng nữa như caching, prefetching, polling, code splitting, ... các bạn hãy đọc thêm document trên trang chủ của thư viện để áp dụng vào dự án.

Tham khảo toàn bộ code mẫu tại đây: https://stackblitz.com/edit/react-router-redux-toolkit-fetch-api-2c64iz?file=src/App.js.

Happy coding 😎

Bình luận

avatar
Phạm Công Định 2021-10-14 02:28:52.357228 +0000 UTC

RTK Query vẫn bị phụ thuộc vào sự cồng kềnh của Redux

Vote React Query :v

Đặng Quang Huy
Đặng Quang Huy 2021-10-14 02:50:23.310277 +0000 UTC

Chờ phần tiếp theo nhé :v

Truong Nguyen
Truong Nguyen 2023-03-23 06:36:44.492238 +0000 UTC

nếu mình dùng React Query thì lại không tận dụng đc redux-toolkit như cách sử dụng extraReducers phải không ạ ?

Nguyễn Ngà
Nguyễn Ngà 2023-03-24 04:29:58.243544 +0000 UTC

builder.addMatcher(api.endpoints.login.matchFulfilled, dùng cái này bạn, mình thấy tương tự 

Phạm Công Định
Phạm Công Định 2023-03-24 09:56:47.518458 +0000 UTC

@Truong Nguyen

Nếu dùng React Query thì cần gì dùng extraReducers nữa bạn? Nó lưu data trong cache store của nó.

Lúc này chỉ cần handle client state => Bạn tự cân nhắc xem có nên dùng redux cho việc này không

Avatar
* Vui lòng trước khi bình luận.
Ảnh đại diện
  +3 Thích
+3