[React Series] Tanstack Query: Data Fetching ที่เปลี่ยนทุกอย่าง

[React Series] Tanstack Query: Data Fetching ที่เปลี่ยนทุกอย่าง

สวัสดีครับทุกคน! วันนี้เราจะมาพูดถึง Tanstack Query (เดิมชื่อ React Query) ซึ่งเป็น Library ที่เปลี่ยนวิธีการจัดการ Data Fetching ใน React ไปอย่างสิ้นเชิง


ทำไมต้อง Tanstack Query?

ลองนึกภาพว่าคุณต้องดึงข้อมูลจาก API ใน React App ของคุณ สิ่งที่คุณต้องทำในแบบดั้งเดิมมีอะไรบ้าง:

  • สร้าง State สำหรับเก็บข้อมูล
  • สร้าง State สำหรับ Loading
  • สร้าง State สำหรับ Error
  • เรียก API ใน useEffect
  • จัดการ Cleanup เมื่อ Component Unmount
  • จัดการ Caching เอง
  • จัดการ Optimistic Updates
  • จัดการ Retry เมื่อ Request ล้มเหลว

ยุ่งยากใช่ไหมครับ? Tanstack Query มาช่วยแก้ปัญหาทั้งหมดนี้!


เริ่มต้นใช้งาน

1. ติดตั้ง

npm install @tanstack/react-query
# หรือ
yarn add @tanstack/react-query

2. Setup Provider

ก่อนใช้งาน ต้องห่อ App ด้วย QueryClientProvider ก่อน:

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
    </QueryClientProvider>
  );
}

3. ใช้งาน useQuery

มาดูตัวอย่างการดึงข้อมูล Users จาก API กัน:

import { useQuery } from '@tanstack/react-query';

function UsersList() {
  const { data, isLoading, error } = useQuery({
    queryKey: ['users'],
    queryFn: async () => {
      const response = await fetch('https://api.example.com/users');
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return response.json();
    },
  });

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

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

เห็นไหมครับ? แค่นี้เราก็ได้ Data Fetching ที่มี Caching, Loading State, และ Error Handling มาให้แล้ว!


Advanced Features

1. Automatic Caching

Tanstack Query มี Cache ให้อัตโนมัติ:

// Component 1: ดึงข้อมูล
const { data } = useQuery({
  queryKey: ['users', userId],
  queryFn: () => fetchUser(userId),
});

// Component 2: อยู่คนละหน้า แต่ใช้ queryKey เดียวกัน
// จะได้ข้อมูลจาก Cache ทันที ไม่ต้องเรียก API ใหม่!
const { data } = useQuery({
  queryKey: ['users', userId],
  queryFn: () => fetchUser(userId),
});

2. Stale Time และ Cache Time

const { data } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
  staleTime: 5 * 60 * 1000, // ข้อมูลจะเป็น "stale" หลังจาก 5 นาที
  gcTime: 10 * 60 * 1000,   // ข้อมูลจะถูกลบออกจาก cache หลังจาก 10 นาที
  refetchOnMount: true,      // refetch ทุกครั้งที่ component mount
  refetchOnWindowFocus: true,// refetch ทุกครั้งที่ window focus
});

3. Manual Refetch

const { data, refetch } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
});

// เรียก refetch เมื่อต้องการ
<button onClick={() => refetch()}>Refresh</button>

4. useMutation สำหรับการแก้ไขข้อมูล

สำหรับการ POST, PUT, DELETE ใช้ useMutation:

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateUser() {
  const queryClient = useQueryClient();

  const mutation = useMutation({
    mutationFn: (newUser) => {
      return fetch('https://api.example.com/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newUser),
      });
    },
    // เมื่อสร้าง user สำเร็จ จะ refetch 'users' query
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['users'] });
    },
  });

  const handleSubmit = (userData) => {
    mutation.mutate(userData);
  };

  return (
    <div>
      {mutation.isPending && 'Creating...'}
      {mutation.isError && `Error: ${mutation.error.message}`}
      {mutation.isSuccess && 'User created!'}
      <button onClick={() => handleSubmit({ name: 'John' })}>
        Create User
      </button>
    </div>
  );
}

5. Optimistic Updates

ทำให้ UI อัพเดททันทีก่อนที่ Server จะตอบกลับ:

const mutation = useMutation({
  mutationFn: updateUser,
  onMutate: async (newUser) => {
    // Cancel any outgoing refetches
    await queryClient.cancelQueries({ queryKey: ['users', newUser.id] });

    // Snapshot the previous value
    const previousUser = queryClient.getQueryData(['users', newUser.id]);

    // Optimistically update to the new value
    queryClient.setQueryData(['users', newUser.id], newUser);

    // Return context with the snapshotted value
    return { previousUser };
  },
  // If the mutation fails, use the context to roll back
  onError: (err, newUser, context) => {
    queryClient.setQueryData(
      ['users', newUser.id],
      context.previousUser
    );
  },
  // Always refetch after error or success
  onSettled: (user) => {
    queryClient.invalidateQueries({ queryKey: ['users', user.id] });
  },
});

6. Parallel Queries

เรียกหลาย queries พร้อมกัน:

import { useQueries } from '@tanstack/react-query';

function Dashboard() {
  const results = useQueries({
    queries: [
      { queryKey: ['users'], queryFn: fetchUsers },
      { queryKey: ['posts'], queryFn: fetchPosts },
      { queryKey: ['notifications'], queryFn: fetchNotifications },
    ],
  });

  // results[0].data = users
  // results[1].data = posts
  // results[2].data = notifications
}

7. Dependent Queries

Query ที่ต้องรอ query ก่อนหน้า:

// ดึง user ก่อน
const { data: user } = useQuery({
  queryKey: ['user', userId],
  queryFn: () => fetchUser(userId),
});

// ดึง posts หลังจากได้ user แล้ว (เมื่อ userId มีค่า)
const { data: posts } = useQuery({
  queryKey: ['posts', userId],
  queryFn: () => fetchPosts(userId),
  enabled: !!userId, // จะไม่ทำงานจนกว่า userId จะมีค่า
});

สรุป

Tanstack Query เป็นเครื่องมือที่ทรงพลังมากสำหรับการจัดการ Data Fetching ใน React ช่วยให้เรา:

  • ลดโค้ด - จากเดิมต้องเขียน state, effect, cleanup เอง ตอนนี้แค่ใช้ useQuery
  • มี Caching อัตโนมัติ - ลดการเรียก API ซ้ำ
  • จัดการ Loading/Error - ง่ายๆ ผ่าน isLoading, isError
  • Optimistic Updates - UI ตอบสนองเร็ว
  • Automatic Retry - ลองใหม่เมื่อ network มีปัญหา

ถ้าคุณยังใช้ useEffect + fetch แบบเดิม ลองหันมาลอง Tanstack Query ดูนะครับ รับรองว่าจะเปลี่ยนวิธีคิดในการทำ Data Fetching ไปตลอด!

ในบทความหน้า เราจะมาดู Advanced Topics เช่น Infinite Queries, React Query DevTools, และการทำ Testing กันครับ


หากมีคำถามหรือต้องการ discuss กันเพิ่มเติม ฝาก comment ไว้ได้เลยครับ! 🚀