Trong thế giới phát triển ứng dụng React, việc quản lý và tối ưu hóa việc lấy dữ liệu là một thách thức quan trọng. React Query nổi bật như một giải pháp mạnh mẽ và dễ sử dụng giúp đơn giản hóa quy trình này. Trong bài viết này, chúng tôi sẽ cung cấp một hướng dẫn toàn diện về React Query, khám phá các tính năng chính của thư viện và cung cấp các ví dụ cụ thể để minh họa cách áp dụng nó trong các dự án thực tế. Từ việc thực hiện các yêu cầu cơ bản cho đến các kỹ thuật nâng cao như phân trang, xử lý lỗi và tối ưu hóa dữ liệu, bạn sẽ tìm thấy tất cả những gì bạn cần để làm chủ React Query và nâng cao hiệu suất ứng dụng React của mình.

1. Cài Đặt React Query

Trước tiên, bạn cần cài đặt React Query trong dự án React của mình. Bạn có thể thực hiện điều này bằng cách sử dụng npm hoặc yarn:

npm install react-query

hoặc

yarn add react-query

2. Cấu Hình Provider

Để sử dụng React Query trong ứng dụng của bạn, bạn cần bao bọc ứng dụng bằng QueryClientProvider từ React Query. Đây là cách bạn cấu hình môi trường cho React Query.

import React from 'react';
import ReactDOM from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient();

ReactDOM.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>,
  document.getElementById('root')
);

3. Fetching Dữ Liệu Cơ Bản

Đây là một ví dụ cơ bản về cách sử dụng React Query để lấy dữ liệu từ một API.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

const TodoList = () => {
  const { data, error, isLoading } = useQuery('todos', fetchTodos);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;

4. Mutations

React Query không chỉ hỗ trợ fetching dữ liệu mà còn cung cấp công cụ để thực hiện các thao tác thay đổi dữ liệu (mutations).

import React from 'react';
import { useMutation, useQueryClient } from 'react-query';
import axios from 'axios';

const addTodo = async (newTodo) => {
  const { data } = await axios.post('https://jsonplaceholder.typicode.com/todos', newTodo);
  return data;
};

const AddTodo = () => {
  const queryClient = useQueryClient();
  const mutation = useMutation(addTodo, {
    onSuccess: () => {
      // Invalidate and refetch
      queryClient.invalidateQueries('todos');
    },
  });

  const handleAddTodo = () => {
    mutation.mutate({ title: 'New Todo', completed: false });
  };

  return (
    <div>
      <button onClick={handleAddTodo} disabled={mutation.isLoading}>
        Add Todo
      </button>
      {mutation.isError && <div>An error occurred: {mutation.error.message}</div>}
      {mutation.isSuccess && <div>Todo added successfully!</div>}
    </div>
  );
};

export default AddTodo;

5. Pagination

React Query hỗ trợ các tình huống phân trang dữ liệu một cách dễ dàng.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async (page = 1) => {
  const { data } = await axios.get(`https://jsonplaceholder.typicode.com/todos?_page=${page}&_limit=10`);
  return data;
};

const PaginatedTodoList = () => {
  const [page, setPage] = React.useState(1);
  const { data, error, isLoading } = useQuery(['todos', page], () => fetchTodos(page), {
    keepPreviousData: true,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <ul>
        {data.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
      <button onClick={() => setPage(old => Math.max(old - 1, 1))} disabled={page === 1}>
        Previous
      </button>
      <button onClick={() => setPage(old => old + 1)}>
        Next
      </button>
    </div>
  );
};

export default PaginatedTodoList;

6. Query Invalidations

Invalidation cho phép bạn làm mới dữ liệu khi một số sự kiện cụ thể xảy ra.

import React from 'react';
import { useQuery, useQueryClient } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

const TodoList = () => {
  const queryClient = useQueryClient();
  const { data, error, isLoading } = useQuery('todos', fetchTodos);

  const handleRefetch = () => {
    queryClient.invalidateQueries('todos');
  };

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button onClick={handleRefetch}>Refetch Todos</button>
      <ul>
        {data.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

7. Query Caching

React Query tự động lưu trữ dữ liệu, giúp giảm số lần yêu cầu không cần thiết.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

const TodoList = () => {
  const { data, error, isLoading } = useQuery('todos', fetchTodos, {
    staleTime: 60000, // Cache dữ liệu trong 60 giây
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {data.map(todo => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

export default TodoList;

8. Background Fetching

Dữ liệu có thể được làm mới trong nền mà không làm gián đoạn trải nghiệm người dùng.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

const TodoList = () => {
  const { data, error, isLoading, isFetching } = useQuery('todos', fetchTodos, {
    refetchInterval: 60000, // Làm mới dữ liệu mỗi phút
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <div>Fetching data in background: {isFetching ? 'Yes' : 'No'}</div>
      <ul>
        {data.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

9. Query Cancellation

React Query hỗ trợ hủy bỏ các yêu cầu đang chờ khi chúng không còn cần thiết nữa.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

const TodoList = () => {
  const { data, error, isLoading, isFetching, refetch } = useQuery('todos', fetchTodos, {
    cancelRefetch: true,
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <button onClick={() => refetch()}>Refetch</button>
      <ul>
        {data.map(todo => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default TodoList;

10. Handling Dependent Queries

React Query cũng hỗ trợ các truy vấn phụ thuộc lẫn nhau.

import React from 'react';
import { useQuery } from 'react-query';
import axios from 'axios';

const fetchUser = async (userId) => {
  const { data } = await axios.get(`https://jsonplaceholder.typicode.com/users/${userId}`);
  return data;
};

const fetchPosts = async (userId) => {
  const { data } = await axios.get(`https://jsonplaceholder.typicode.com/posts?userId=${userId}`);
  return data;
};

const UserPosts = ({ userId }) => {
  const { data: user } = useQuery(['user', userId], () => fetchUser(userId));
  const { data: posts, error, isLoading } = useQuery(['posts', userId], () => fetchPosts(userId), {
    enabled: !!user, // Chỉ chạy truy vấn posts khi user có sẵn
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{user.name}'s Posts</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
};

export default UserPosts;


Kết Luận

React Query là một công cụ mạnh mẽ giúp quản lý và tối ưu hóa việc lấy dữ liệu trong các ứng dụng React. Với tính năng caching, background fetching, và query invalidation, React Query giúp cải thiện hiệu suất và trải nghiệm người dùng. Các ví dụ trên cho thấy cách sử dụng React Query trong nhiều tình huống khác nhau, từ fetching dữ liệu cơ bản đến xử lý pagination, mutations, và các truy vấn phụ thuộc. Hãy thử nghiệm với các ví dụ này để làm quen với các tính năng của React Query và tích hợp chúng vào ứng dụng của bạn.