- vừa được xem lúc

React Query: Quản lý cache data dễ dàng hơn bao giờ hết

0 0 1

Người đăng: Nguyen Le Than

Theo Viblo Asia

React-Query

React-query thường được sử dụng để quản lý, lưu trữ các dữ liệu được truy vấn từ server một cách hiệu quả. Một số tính năng chính của react-query như: gọi API ( fetching data ), tự động cache dữ liệu từ API trả về, tự động refetch khi dữ liệu stale ( bị lỗi thời ),...

Installation: npm i @tanstack/react-query pnpm add @tanstack/react-query yarn add @tanstack/react-query bun add @tanstack/react-query

Ví dụ cơ bản về react-query

Trong ví dụ này sẽ bao gồm 3 khái niệm chính của react-query là Queries, Mutation, Query Invalidation.

import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider,
} from '@tanstack/react-query' // Create a client
const queryClient = new QueryClient() function App() { return (
// Provide the client to your App <QueryClientProvider client={queryClient}> <Todos /> </QueryClientProvider> )
}
render(<App />, document.getElementById('root'))
import { useQuery, useMutation, useQueryClient, QueryClient, QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api' function Todos() {
// Truy cập vào client const queryClient = useQueryClient() // Query: Tạo query dùng để call API getTodos với key là "todos" const query = useQuery({ queryKey: ['todos'], queryFn: getTodos }) // Mutations: Tạo mutation dùng để call API postTodo const mutation = useMutation({ mutationFn: postTodo, onSuccess: () => {
// Invalidate and refetch: Sau khi call API postTodo thành công sẽ tiến hành invalidate data để thực hiện gọi lại API getTodos để refetch dữ liệu mới.
// Lưu ý: React-query sử dụng queryKey để biết cần phải invalidate query nào queryClient.invalidateQueries({ queryKey: ['todos'] }) }, }) return ( <div> <ul> {query.data?.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> <button onClick={() => { mutation.mutate({ id: Date.now(), title: 'Do Laundry', }) }} > Add Todo </button> </div> )
}

Queries

Queries được sử dụng để fetch data từ server, ví dụ như method GET.

Ví dụ:

import { useQuery } from '@tanstack/react-query'
function App() { const { isFetching, isPending, isError, isRefetching, isSuccess, error, data } = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList })
}

Trong đó, cần truyền vào 2 giá trị đó là:

  • queryKey: Định nghĩa một unique key cho query, có thể truyền một hoặc nhiều key. Key này được dùng để refetch data, caching, chia sẽ query cho toàn bộ ứng dụng
  • queryFn: Là hàm gọi API.

Gía trị trả về của query bao gồm:

  • isPending : Query chưa có dữ liệu
  • isError : Query nhận về lỗi
  • isSuccess : Query thành công và dữ liệu đã được sẵn sàng
  • error - Chứa lỗi.
  • data - Chứa dữ liệu.
  • isFetching - Trạng thái đang gọi api.

Lưu ý*: Nếu bạn có thắc mắc khi nào nên sử dụng isPending, isFetching, isRefetching, mình sẽ giải thích ở cuối bài viết.

Ví dụ về việc sử dụng các giá trị trên:

function Todos() { const { isPending, isError, data, error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodoList, }) if (isPending || isFetching) { return <span>Loading...</span> } if (isError) { return <span>Error: {error.message}</span> }
// Có thể hiểu rằng tại thời điểm này isSuccess = true nên không cần kiểm tra điều kiện return ( <ul> {data.map((todo) => ( <li key={todo.id}>{todo.title}</li> ))} </ul> )
}

Query Key

Query Key là một chuỗi các giá trị dùng để định danh mỗi query dữ liệu. Nó giúp React Query tìm và quản lý các yêu cầu trong Query Cache.

Hãy đảm bảo việc truyền key hợp lý cho từng query để việc caching data hoạt động đúng. Đối với React-Query việc caching dựa vào queryKey, giả sử gọi useQuery có query key giống nhau ở 2 nơi khác nhau thì chỉ call API 1 lần, vì data đã được caching.

Lưu ý: Để useQuery thực thi lại hàm queryFn với params mới nhất thì cần truyền giá trị vào cả queryKey và queryFn.

Ví dụ:

 const [page, setPage] = useState(0) const fetchProjects = (page = 0) => fetch('/api/projects?page=' + page).then((res) => res.json()) const { error, data, isFetching, } = useQuery({ // useQuery sẽ thực hiện gọi API mỗi lần page thay đổi giá trị  queryKey: ['projects', page], queryFn: () => fetchProjects(page), })

Dependent Queries

Đặt điều kiện cho query, khi nào thỏa mãn điều kiện thì query mới được thực thi. Thường được sử dụng trong trường hợp phụ thuộc vào query trước đó phải hoàn thành trước rồi mới thực hiện.

Sử dụng tính năng này bằng cách thêm key enabled

Ví dụ:

// Tạo query để get user bằng email
const { data: user } = useQuery({ queryKey: ['user', email], queryFn: getUserByEmail,
}) const userId = user?.id
// Sau đó lấy tất cả projects của user đó bằng userId const { status, fetchStatus, data: projects,
} = useQuery({ queryKey: ['projects', userId], queryFn: getProjectsByUser,
// Query sẽ không thực hiện nếu không có userId enabled: !!userId,
})

Mutations

Không giống như useQuery, mutations thường được sử dụng cho các method POST/PUT/PATCH/DELETE.

Ví dụ:

function App() {
// Khởi tạo mutation bằng useMutation const mutation = useMutation({ mutationFn: (newTodo) => { return axios.post('/todos', newTodo) }, }) return ( <div> {mutation.isPending ? ( 'Adding todo...' ) : ( ****<> {mutation.isError ? ( <div>An error occurred: {mutation.error.message}</div> ) : null} {mutation.isSuccess ? <div>Todo added!</div> : null} <button onClick={() => { // Gọi mutationFn với các tham số như id, title mutation.mutate({ id: new Date(), title: 'Do Laundry' }) }} > Create Todo </button> </> )} </div> )
}

Một vài giá trị mà useMutation trả về:

  • isIdle or status === 'idle' - mutation đang rảnh rỗi không làm gì
  • isPending or status === 'pending' - mutation đang chạy
  • isError or status === 'error' - mutation chạy xong và gặp lỗi
  • isSuccess or status === 'success' - mutation chạy xong và thành công
  • error - lấy ra lỗi khi mutation lỗi
  • data - lấy ra data khi mutation thành công
  • mutate - là một hàm đại diện cho mutationFn, khi gọi hàm mutate thì hàm mutationFn sẽ được thực thi
  • reset - là một hàm dùng đễ clear data hoặc error

Mutation Side Effect

Chúng ta có thể thực thi một đoạn code vào các giai đoạn vòng đời mutations để thực hiện các tác vụ khác nhau bằng các tham số truyền vào useMutation như: onMutate, onError, onSuccess, onSettled.

useMutation({ mutationFn: addTodo, onMutate: (variables) => { // Khi mutationFn bắt đầu được thực thi }, onError: (error, variables, context) => { // Khi thực thi mutationFn xảy ra lỗi }, onSuccess: (data, variables, context) => { // Khi thực thi mutationFn thành công }, onSettled: (data, error, variables, context) => { // Khi thực thi mutationFn thành công hoặc lỗi },
}) 

Cơ chế Caching trong React-Query

Dưới đây là ví dụ cơ bản về cơ chế caching:

Giả sử chúng ta đang sử dụng cacheTime mặc định là 5 phút và staleTime mặc định là 0.

Một phiên bản đầu tiên của useQuery({ queryKey: ['todos'], queryFn:fetchTodos }) được khởi tạo ở component A.

  • Vì không có truy vấn nào khác được thực hiện bằng khóa truy vấn ['todos'] nên truy vấn này sẽ thực thi.
  • Khi call API xong, dữ liệu trả về sẽ được lưu vào bộ đệm có key là ['todos'].

Phiên bản thứ hai của useQuery({ queryKey: ['todos'], queryFn:fetchTodos }) được khởi tạo ở component B.

  • Vì bộ đệm đã có dữ liệu cho key ['todos'] từ truy vấn ở component A nên dữ liệu đó sẽ được trả về ngay lập tức từ bộ đệm mà không cần gọi lại API.
  • Lưu ý rằng trạng thái của cả hai truy vấn ở component A và B đều được cập nhật (bao gồm isFetching, isLoading và các giá trị liên quan khác) vì chúng có cùng key truy vấn.
  • Khi có yêu cầu gọi API mới, dữ liệu của bộ đệm với khóa ['todos'] sẽ được cập nhật dữ liệu mới và cả hai phiên bản component A,B đều được cập nhật dữ liệu mới.

Một số trường hợp sử dụng react-query trong thực tế

isFetching Global

const isFetching = useIsFetching() : Lấy trạng thái isFetching của bất kỳ query nào đang thực hiện fetch data trong ứng dụng, isFetching trả về số lượng query đang fetching ( trả về số thay vì true/false )

Refetch data khi focus vào Window

// Global
const queryClient = new QueryClient({ defaultOptions: { queries: { refetchOnWindowFocus: false,// default: true }, },
})
function App() { return <QueryClientProvider client={queryClient}>...</QueryClientProvider>
} useQuery({ queryKey: ['todos'], queryFn: fetchTodos, refetchOnWindowFocus: false,
})

Can thiệp vào việc khi nào useQuery được phép thực thi bằng cách sử dụng enable

function Todos() { const [start, setStart] = useState(false) const { data } = useQuery({ queryKey: ['todos', filter], queryFn: () => fetchTodos(filter), // Sẽ không thực hiện query cho đến khi start = true enabled: start }) return ( <div> <button onClick={() => setStart(true)}>Start Query</button> {data && <TodosTable data={data}} /> </div> )
} 

Retry query khi xảy ra lỗi

Ví dụ: Thực hiện lại 10 lần mỗi lần delay 1s nếu query bị lỗi, nếu sau 10 lần vẫn còn lỗi thì sẽ dừng và trả về kết quả lỗi

import { useQuery } from '@tanstack/react-query'
const result = useQuery({ queryKey: ['todos', 1], queryFn: fetchTodoListPage, retry: 10,// Query sẽ retry 10 lần, sau đó ở lần 11 vẫn bị lỗi thì query sẽ trả về kết quả lỗi retryDelay: 1000, // Thời gian delay mỗi lần retry
})

Một số cách Invalidation query

Sau khi thực hiện Invalidation thì những query này sẽ bị đánh dấu là cũ, khi component chứa query đó đc mount thì query sẽ thực hiện refetch để cập nhật data mới.

// Invalidate tất cả query có trong cache
queryClient.invalidateQueries() // Invalidate mỗi query có key bắt đầu bằng todos
queryClient.invalidateQueries({ queryKey: ['todos'] }) // Reset mỗi query có key bắt đầu bằng todos
queryClient.resetQueries({ queryKey: ['todos'] }) // Invalidate sau 5s
cacheTime: 5000 

Làm mới dữ liệu sau khi tạo record mới

import { useMutation, useQueryClient } from '@tanstack/react-query'
const queryClient = useQueryClient()
// Sau khi addTodo thành công sẽ tiến hành invalidate query có key là "todos" và "reminders" để làm mới dữ liệu 
const mutation = useMutation({ mutationFn: addTodo, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['todos'] }) queryClient.invalidateQueries({ queryKey: ['reminders'] }) },
}) 

Get data và Set data của useQuery

Get dùng để lấy ra data dựa vào queryKey.

Set được sử dụng để cập nhập lại data query sau khi thực hiện chỉnh sửa record mà không cần phải thực thi lại useQuery.

// Set data vào query có key là todo
queryClient.setQueryData(['todo'], data) // Get data của query có key là todo
queryClient.getQueryData(['todo'])

staleTime

Được truyền vào useQuery để xác định thời gian khi nào data trở thành data cũ, nếu data được đánh dấu là cũ thì khi component chứa useQuery đc mount thì useQuery sẽ thực hiện refetch để cập nhật data mới.

cacheTime

Là thời gian React Query giữ lại dữ liệu trong cache sau khi không còn component nào dùng query đó nữa. Sau thời gian này, toàn bộ data của query đó sẽ bị xóa khỏi bộ nhớ.

Sử dụng các trạng thái loading như thế nào?

Đối với useQuery:

useQuery có 2 khái niệm về loading như sau:

  • Loading lần đầu - query chưa có trong cache, thực hiện call lần đầu.
  • Loading nền - query đã có trong cache, thực hiện revalidate data.

Phân biệt isLoading, isFetching, isRefetching:

isLoading: Loading lần đầu

isFetching: Loading lần đầu || Loading nền

isRefetching: Loading nền

Đối với useMutation:

isLoading ⇒ chỉ có một trạng thái loading nên không cần phân biệt

Tài liệu tham khảo

Bình luận

Bài viết tương tự

- vừa được xem lúc

Một vài thủ thuật CSS mà chính Frontend có thể còn chưa biết (Phần 30)

. Hello xin chào mọi người, mình đã trở lại và tiếp tục với phần 30 của series về Một vài thủ thuật CSS mà chính Frontend có thể còn chưa biết. Bắt đầu thôi nào.

0 0 48

- vừa được xem lúc

5 câu hỏi phỏng vấn Frontend giúp bạn tự tin hơn khi sử dụng bất đồng bộ trong Javascript

Một trong những điều khó khăn khi học Javascript là promises. Chúng không dễ hiểu và có thể cần một vài hướng dẫn và một thời gian kha khá để vận dụng chúng.

0 0 104

- vừa được xem lúc

Một vài thủ thuật CSS mà chính Frontend có thể còn chưa biết (Phần 31)

Hello xin chào mọi người, mình đã trở lại và tiếp tục với phần 31 của series về Một vài thủ thuật CSS mà chính Frontend có thể còn chưa biết. Bắt đầu thôi nào.

0 0 46

- vừa được xem lúc

Những lý do khiến mình thích CSS custom properties hơn SASS variables?

Halo các bạn,. Lại là mình với một bài post liên quan tới chủ đề Front-end đây Mình còn nhớ hồi mình bắt đầu tìm hiểu và bị SASS lôi cuốn, mình đã nghĩ rằng mình sẽ chẳng bao giờ cần dùng đụng tới CSS

0 0 91

- vừa được xem lúc

Usability là gì? Những lưu ý khi thiết kế Usability

Usability là một yếu tố quan trọng trong sự thành bại của sản phẩm. Thật đáng tiếc khi sản phẩm làm ra ưu việt về tính năng, nhưng lại không được người dùng tiếp nhận, đơn giản chỉ vì khó sử dụng.

0 0 38

- vừa được xem lúc

7 Repos cho Front-End mà chính bạn còn không biết là bạn cần nó

. Những repos chẳng mấy khi được nhắc đến nhưng lại giúp bạn build mọi thứ nhanh hơn và tốt hơn nhiều. Chúng ta đang sống trong một thời đại có sẵn các công cụ và tài nguyên hoàn hảo, chúng chỉ cách t

0 0 40