Trang chủ

/

Blogs
avatar

Lập trình thật dễ

Jul 30, 2024

6 phút đọc


React QueryReactJS

React Query - TanStack Query là gì? Tại sao nên sử dụng react query ở Frontend

React Query - TanStack Query là gì? Tại sao nên sử dụng react query ở Frontend - Quản lý server state - Lập trình thật dễ

1.Giới thiệu

Trong 1 ứng dụng web ở phía Frontend, việc quản lí global state là 1 việc cần thiết. 

Global state có thể chia làm 2 loại:

Client state: là các trạng thái ở phía client, VD: trạng thái đóng/mở popup, modal, value input/select…

Server state: là các trạng thái ở phía server, VD: là các dữ liệu được trả về từ API (database)

TanStack Query - React query được sinh ra để giúp quản lí server state ở Frontend, các tính năng nổi bật của nó như: fetching, caching, synchronizing,… sẽ giúp chúng ta cải thiện trải nghiệm người dùng cũng như tăng performance của project.

Các khái niệm cần biết để bài viết này dễ hiểu hơn

Cache: bộ nhớ đệm lưu trữ dữ liệu 

refetching: chính là quá trình call api và cập nhật dữ liệu vào trong cache

useQuery: hook dùng để fetching các API method GET

useMutation: hook dùng để fetching các API method PUT, PATCH,DELETE, POST

2.Lý do tại sao chúng ta nên sử dụng react-query thay vì dùng các hook của React cho tiện?

Để fetch dữ liệu từ server thì có nhiều cách. Một số cách phổ biến là sử dụng useEffect  và useState hay sử dụng thư viện quản lí state (Redux)

 const Component = () => {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(false)
    const [isError, setIsError] = useState(false)

    useEffect(() => {
      const getData = async () => {
        await fetch("https://example.com/api")
          .then(res => {
            const response = await res.json()
            setData(response)
            setLoading(false)
            setLoading(false)
          }).catch(() => {
            setIsError(true)
            setLoading(false)
          })
      }
      getData()
    })

    return (
      // Some JSX here
    )
  }

Ở đoạn code trên, chúng ta thấy rằng nó sẽ rất bình thường và hoạt động vẫn ổn, tuy nhiên lại có nhược điểm là khá rờm rà, chúng ta phải code khá nhiều cũng như tạo các state quản lý trạng thái của việc API fetching (success, error, loading,…)

Bây giờ chúng ta sẽ dùng react-query để cùng sử dụng 1 tác vụ để xem sự khác nhau:

Chúng ta sẽ dùng hook trong tanStack Query V5 là useQuery (với hook này chúng sẽ chỉ dùng với các api có method là GET - tức là các API lấy dữ liệu)

Để sử dụng hook useQuery - version mình đang dùng là version 5, ta phải truyền 2 tham số bắt buộc là:

queryKey:  Tham số này sẽ giúp ta phân biệt các query với nhau (1 query = 1 fetching API(1 queryKey có thể gọi là 1 query), chúng ta có thể dựa vào queryKey để refetching, caching, lấy dữ liệu giữa các component bằng queryKey (chúng ta sẽ tìm hiểu ở dưới nha)

queryfn: chính là một hàm fetching dữ liệu từ API (bắt buộc phải trả về 1 promise)

Tham số thứ 3 là các options (không bắt buộc) - nó sẽ giúp ta settings cho các query. (chúng ta sẽ tìm hiểu bên dưới nhé)

const Component = () => {
    const fetchData = async () => {
      const res = await fetch("https://example.com/api")
      const data = await res.json()
      return data
    }

    const {data, isPending, isError, isSuccess} = useQuery({
      queryKey: ['example'],
      queryFn: fetchData
    })

    return (
      // Some JSX here
    )
  }

Các bạn thấy khi dùng useQuery để fetching thì nó sẽ clean hơn, ngoài các thông tin như (data - dữ liệu hàm fetching trả về, isError - bằng true khi API lỗi, isSuccess = true khi status API trả về success, isPending - trạng thái pending của API)

Ngoài ra còn có thể thông tin khác được trả về nữa, bạn vào đây để xem thêm.

3.Tìm hiểu cách hoạt động của react query 

Trước tiên chúng ta sẽ tìm hiểu về cách hoạt động của react query: Khi chúng ta thực hiện fetching bằng react query, thì lúc mà dữ liệu được trả về từ hàm fetching => nó sẽ được lưu vào trong cache(có thể setting bằng options) - sau đó dữ liệu được trả về từ tham số data.

Chắc mọi người sẽ thắc mắc nó lưu vào trong cache chi đúng không? 

Đầu tiên mình fetching 1 API list user, sau đó mình sẽ click vào chi tiết 1 user(đồng nghĩa lúc này mình cần sẽ phải call 1 API details user nữa) - thay vì bắt người dùng phải ngồi đợi api details user trả về => thì mình có thể dùng dữ liệu trong cache (query bằng id) để hiện thị tạm thời( thay cho loading) đến khi nào response của api details trả về thì mình sẽ thay thế = >trải nghiệm người dùng sẽ được tối ưu.

4.useMutation

Khác với useQuery, useMutation dùng với các api thay đổi dữ liệu ở server

Các tham số sẽ gần giống với useQuery

mutationKey: chính là là key để phân biệt các mutate với nhau

mutationFn: chính là hàm fetching

Tham số thứ 3 là các options (không bắt buộc) - nó sẽ giúp ta settings cho các query. (chúng ta sẽ tìm hiểu bên dưới nhé)

function App() {
    const addTodo = async (data) => {
    await serviceAddTodo(data)
  }
  
  const mutation = useMutation({
    mutationKey: ['todoo'],
    mutationFn: (data) => addTodo(data),
    onSuccess: (data) => {
    // Chính là response nhận được từ hàm services
    console.log(data)
    },
    onError: (error: any) => {
      // Chính là response (error) nhận được từ hàm services
    console.log(error)
    },
     onSettled: () => {
      console.log("giống như finally trong promise -  dù success hay failed đều chạy vào")
    }
  })
 
   return (
     <div>
       {mutation.isLoading ? (
         'Adding todo...'
       ) : (
         <>
           {mutation.isError ? (
             <div>An error occurred: {mutation.error.message}</div>
           ) : null}
 
           {mutation.isSuccess ? <div>Todo added!</div> : null}
 
           <button
             onClick={() => {
               mutation.mutate({ id: new Date(), title: 'Do Laundry' })
             }}
           >
             Create Todo
           </button>
         </>
       )}
     </div>
   )
 }

Cũng như useQuery, useMutation trả về những thông tin cần thiết như isLoading hay isError. (Bạn có thể xem thêm một số thông tin khác ở đây). Ngoài ra khi mọi người tạo lại api list todos để cập nhật lại todo mới tạo thì có thể dùng cách này để tối ưu nha.

const queryClient = useQueryClient()
trong hàm onSuccess của useMutation
  onSuccess: (data) => {
      // cách 1
      queryClient.invalidateQueries(['key của list todos']) // nó sẽ refetch lại danh sách todos
      // cách 2 không cần call lại api
      queryClient.setQueryData(
        ['key của list todo'],
        (oldData: any) => {
        // olđData là dữ liệu hiện tại trong cache
        // ngoài old data bạn có thể dùng như này để lấy dữ liệ thông qua query key
		queryClient.getQueryState([QUERY_KEY.ITEM.GET_LIST, +itemId]) 
          return [...oldData, newTOdo] //sử lý để trả về dữ liệu mới trong cache
        })
    },

5.Các options cơ bản

1.retry: Số lần thử lại khi có lỗi 

2.refetchOnWindowFocus:  Có refetch khi focus lại vào cửa sổ không 

3.staleTime: Thời gian dữ liệu được xem là stale (khi nó stale thì nó sẽ được refetching lại, có thể dùng để lấy details, khi nào có id thì sẽ kích hoạt)

4.gcTime(cacheTime):Thời gian dữ liệu được giữ trong cache

5.enabled: Boolean, kiểm soát xem query có được enabled hay không.

6.refetchOnReconnect: Boolean, có refetch khi kết nối lại không

7.refetchOnMount: Boolean, có refetch khi component mount không.

8.initialData: Giá trị ban đầu cho query.(nó sẽ lưu vào cache)

9.placeholderData: Dữ liệu tạm thời cho đến khi query được resolved. (nó sẽ không lưu vào cache)

6.Kết

react-query(tanStack query) là một thư viện rất đáng để thử, còn rất nhiều tính năng hay của react-query mà mình chưa đề cập tới, bạn có thể xem docs của react-query ở đây:

Docs

Hoặc có thể học qua khóa học này bên mình.

Khóa học