🌞

Sử dụng React-Query để fetch data

Sửa bài viết này

Đã bao giờ anh em react chúng ta rơi vào cảnh sau: thử tới thử lui bao nhiều là thư viện để quản lý global state, thử bao nhiêu là hook useFancyPromise, rồi tự viết những giải pháp riêng, tất cả điều dẫn đến một kết cục: viết cả đống code để xử lý việc fetch data?

Server state và client state cơ bản là khác nhau, vậy tại sao lại trộn chung

Đặc điểm của Server state

  1. cố định, ngoài khả năng kiểm soát của client
  2. bất đồng bộ (async), cần fetch để lấy dữ liệu hoặc cập nhập
  3. chia sẽ, nhiều người cùng truy xuất đến cùng một tập dữ liệu cũng như thao tác xử lý trên tập dữ liệu này

Đến lúc dừng việc trộn server state vào trong client state

Lợi ích mang lại khi tách việc quản lý server state bằng một công cụ riêng

  1. cache, phần khó nhất của lập trình
  2. Không duplicate nhiều request
  3. Cập nhập ngầm các dữ liệu outdate
  4. Tối ưu hiệu năng bằng phân trang, lazy load
  5. Quản lý memory và garbage collection tốt hơn

Xin giới thiệu với bạn người bạn mới React-query, anh này sẽ cung cấp cho chúng ta

  • Một global context để lưu trữ dữ liệu lấy về từ server
  • Thiết đặt caching vô cùng đơn giản

Nếu thích bạn cũng có thể tham khảo thêm swr cũng khá cool

Chúng ta sẽ có các Server side APIs sau

// api/product.js

// 1. Fetch tất cả products
const useFetchProducts = () => {}

// 2. Fetch một product cụ thể
const useFetchProduct = (id) => {}

// 3. Thêm một product
const useAddProduct = (product) => {}

// 4. Cập nhập một product
const useEditProduct = (product) => {}

// 5. Xóa một product
const useDeleteProduct = (id) => {}

Thực hiện fetch với useQuery

import { useQuery } from 'react-query'

const useFetchProducts = () => {
	return useQuery(
		// định danh
		'products',		() => {
			fetch('/api/products')
		}
	)
}

Sử dụng trong component

import { useFetchProducts } from "../api/products"

const Products = () => {
	const {
		data: products,
		isLoading
	} = useFetchProducts();

	return (
		<div>
			{
				isLoading && <div>Loading...</div>
			}
			{
				products && (
					products.map((product) => {
						<div key={product.id}>
							{product.name}
						</div>
					})
				)
			}
		</div>
	)
}

Việc fetch dữ liệu sẽ còn thêm các tính năng như search, phân trang, filter. Có react-query mọi thứ sẽ vô cùng đơn giản

import { useState } from "react"
import { useFetchProducts } from "../api/products"

const Products = () => {
	// trang hiện tại
	const [page, setPage] = useState(1)
	// số item trên trang
	const [limit, setLimit] = useState(10)
	// từ khóa
	const [name, setName] = useState('')		
	const {
		data: products,
		isLoading
	} = useFetchProducts({
		page,		limit,		name	});

	return (
		<div>
			{
				isLoading && <div>Loading...</div>
			}
			{
				products && (
					products.map((product) => {
						<div key={product.id}>
							{product.name}
						</div>
					})
				)
			}
		</div>
	)
}

Chúng ta cần cập nhập lại useFetchProducts

import { useQuery } from 'react-query'

const useFetchProducts = ({ page, limit, name }) => {
	return useQuery(
		['products', { page, limit, name }],		() => {
			fetch(`/api/products?page=${page}&limit=${limit}&search=${name}`)		}
	)
}

Thực hiện cache

Ví dụ chúng ta muốn đặt cache 10s, chúng ta sẽ sử dụng thiết đặt staleTime

import { useQuery } from 'react-query'

const useFetchProducts = ({ page, limit, name }) => {
	return useQuery(
		['products', { page, limit, name }],
		() => {
			fetch(`/api/products?page=${page}&limit=${limit}&search=${name}`)
		},
		{
			staleTime: 10000		}
	)
}

Cực kỳ đơn giản đúng không!

Tưởng tượng chúng ta có danh sách product hiển thị trên màn hình, click vào một product chúng ta hiển thị pop-up với các thông tin của product

Để fetch một product, chúng ta cũng đồng thời áp dụng cache

const useFetchProduct = (id) => {
	return useQuery(
		['product', id],
		() => {
			fetch(`/api/products/${id}`)
		},
		{
			staleTime: 10000
		}
	)
}

Đến phần thú vị nè, nếu các thông tin của từng product hoàn toán giống với thông tin trả về từ danh sách product?, chúng ta có thể áp dụng cache cho từng product trong lúc fetch danh sách product

import { useQuery, useQueryClient } from 'react-query'

const useFetchProducts = ({ page, limit, name }) => {
	const queryClient = useQueryClient();
	return useQuery(
		['products', { page, limit, name }],
		() => {
			fetch(`/api/products?page=${page}&limit=${limit}&search=${name}`)
		},
		{			
			staleTime: 10000,
			onSuccess: (products) => {				products.forEach(product => {
					queryClient.setQueryData(						['product', product.id],
						product
					);
				})
			}
		}
	)
}

Bằng cách dùng useQueryClient().setQuery, chúng ta force cache cho từng product.id, để khi useFetchProduct chạy nó sẽ có sẵn giá trị cache này và không cần thực hiện gọi API

Thể hiện chút tình yêu với dự án react-query nhé các bạn, star ngay không nói nhiều