0Pricing
React Native Academy · บทเรียน

การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง

ใช้ useMutation เพื่อส่งข้อมูลด้วย POST ทำให้คำค้นที่เกี่ยวข้องไม่ถูกต้องเมื่อสำเร็จเพื่อให้รายการรีเฟรชโดยอัตโนมัติ และแสดงผลตอบสนองของ UI เชิงคาดการณ์ขณะที่คำขอกำลังทำงาน

การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง เป็นบทเรียน React Native Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน React Native Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

What Is a Mutation?

In React Query terminology, a mutation is any operation that changes server data — POST, PUT, PATCH, or DELETE requests. While useQuery fetches and caches data, useMutation handles data modification with loading, error, and success states, plus powerful side-effect callbacks.

Separating queries (reads) from mutations (writes) is a key conceptual pattern. Mutations don't cache their results — they trigger side effects and then cause related queries to refetch fresh data.

useMutation Basics

useMutation takes a mutationFn — an async function that performs the write operation. It returns a mutate function (or mutateAsync for Promise-based usage) that you call when the user triggers the action.

Unlike useQuery, mutations don't run automatically. They wait for you to call mutate(variables). The hook provides isPending, isError, isSuccess, error, and data state from the mutation's response.

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

async function createPost(newPost) {
  const response = await fetch('https://api.example.com/posts', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newPost),
  });
  if (!response.ok) throw new Error('Failed to create post');
  return response.json();
}

const { mutate, isPending, isError } = useMutation({
  mutationFn: createPost,
});

Calling mutate and mutateAsync

Call mutate(variables) to trigger the mutation. The variables argument is passed directly to your mutationFn. mutate is fire-and-forget — it doesn't return a Promise. Use the hook's onSuccess and onError callbacks to respond to the result.

Use mutateAsync(variables) instead when you need to await the result in an async function (e.g., to navigate after a successful post). Always wrap mutateAsync in try/catch — unhandled rejections will cause errors.

const { mutate, mutateAsync, isPending } = useMutation({
  mutationFn: createPost,
});

// Using mutate (fire-and-forget):
function handleSubmit() {
  mutate({ title: 'My Post', body: postContent });
}

// Using mutateAsync (awaitable):
async function handleSubmitAndNavigate() {
  try {
    const newPost = await mutateAsync({ title: 'My Post', body: postContent });
    navigation.navigate('PostDetail', { id: newPost.id });
  } catch (err) {
    Alert.alert('Error', err.message);
  }
}

onSuccess: Callback After Mutation

The onSuccess callback fires when the mutation completes successfully. It receives the mutation result data and the variables that were passed to mutate. Use onSuccess to invalidate related queries, show success feedback, reset forms, or navigate away.

You can define onSuccess at the hook level (for logic that always applies) and also pass it as a second argument to mutate(variables, { onSuccess }) for per-call customization.

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

const queryClient = useQueryClient();

const { mutate } = useMutation({
  mutationFn: createPost,
  onSuccess: (newPost, variables) => {
    // Refresh the posts list:
    queryClient.invalidateQueries({ queryKey: ['posts'] });
    // Show success:
    Alert.alert('Post created!', newPost.title);
    // Reset form:
    resetForm();
  },
  onError: (error) => {
    Alert.alert('Error', error.message);
  },
});

Cache Invalidation After Mutation

Cache invalidation is the process of marking cached queries as stale after a mutation so they refetch fresh data. Use queryClient.invalidateQueries with a query key. Partial key matching is supported — invalidating ['posts'] invalidates all queries whose key starts with ['posts'], including ['posts', userId].

Invalidation triggers background refetches for all active (mounted) queries with matching keys. Components using those queries will show fresh data within one network round-trip.

const { mutate: deletePost } = useMutation({
  mutationFn: (postId) =>
    fetch('/api/posts/' + postId, { method: 'DELETE' }),
  onSuccess: (data, postId) => {
    // Invalidate the posts list:
    queryClient.invalidateQueries({ queryKey: ['posts'] });
    // Also invalidate the specific post's cache:
    queryClient.invalidateQueries({ queryKey: ['post', postId] });
  },
});

Optimistic Updates

Optimistic updates immediately update the UI before the server confirms the mutation. If the mutation succeeds, the speculative change is confirmed. If it fails, the UI rolls back to the previous state. This makes interactions feel instantaneous — no waiting for the server.

Implement optimistic updates in onMutate: save the current cache snapshot, update the cache immediately, then roll back in onError if needed. React Query provides queryClient.setQueryData to manually update the cache.

const { mutate: likePost } = useMutation({
  mutationFn: (postId) => fetch('/api/posts/' + postId + '/like', { method: 'POST' }),
  onMutate: async (postId) => {
    await queryClient.cancelQueries({ queryKey: ['posts'] });
    const snapshot = queryClient.getQueryData(['posts']);
    // Optimistically add like:
    queryClient.setQueryData(['posts'], (old) =>
      old.map(p => p.id === postId ? { ...p, likes: p.likes + 1 } : p)
    );
    return { snapshot }; // return for rollback
  },
  onError: (err, postId, context) => {
    queryClient.setQueryData(['posts'], context.snapshot); // rollback
  },
});

Mutation Status and UI Feedback

Use mutation status fields to provide appropriate UI feedback. isPending is true while the mutation is in flight — disable the submit button and show a spinner. isSuccess is true after completion — show a success message. isError is true if it failed — show the error message.

React Query resets mutation status automatically after a configurable gcTime period. To reset it manually (e.g., to allow the user to retry), call the reset() function returned by the hook.

const { mutate, isPending, isError, isSuccess, error, reset } = useMutation({
  mutationFn: submitForm,
});

return (
  <View>
    <TextInput value={text} onChangeText={setText} editable={!isPending} />
    <Button
      title={isPending ? 'Submitting...' : 'Submit'}
      onPress={() => mutate({ text })}
      disabled={isPending}
    />
    {isError && <Text style={{ color: 'red' }}>{error.message}</Text>}
    {isSuccess && <Text style={{ color: 'green' }}>Saved!</Text>}
  </View>
);

Updating Cache Directly After Mutation

Instead of invalidating and refetching, you can directly update the cache with the mutation response using queryClient.setQueryData. This is more efficient when the server returns the updated data in the mutation response — you can add it to the list without an extra network request.

This pattern — set cache from server response rather than invalidate — saves a network round-trip and is useful for lists where you want to append the new item immediately.

const { mutate } = useMutation({
  mutationFn: createPost,
  onSuccess: (newPost) => {
    // Append new post to cached list directly:
    queryClient.setQueryData(['posts'], (oldPosts) => {
      if (!oldPosts) return [newPost];
      return [newPost, ...oldPosts]; // prepend new post
    });
    // No invalidation needed — cache is already up to date!
  },
});

Global Mutation Callbacks via QueryClient

You can define global mutation callbacks in the QueryClient's defaultOptions.mutations for logging, error reporting, or token refresh that should apply to every mutation in the app. Local mutation options override global ones — both can coexist.

A common use case is a global onError callback that checks for 401 Unauthorized responses and triggers a logout or token refresh flow for every mutation, without repeating this logic in each individual useMutation call.

const queryClient = new QueryClient({
  defaultOptions: {
    mutations: {
      onError: (error) => {
        // Global error handling for all mutations:
        if (error.status === 401) {
          handleUnauthorized();
        } else {
          console.error('Mutation error:', error.message);
        }
      },
    },
  },
});

Mutations with File Uploads

Mutations handle file uploads by accepting FormData as the variables. The mutationFn creates a FormData object and sends it with fetch using the multipart/form-data content type. React Query manages the loading and error state the same way as any other mutation.

For large file uploads, track progress using a custom fetch wrapper with XHR, storing progress in a separate state variable updated via the onUploadProgress pattern.

const { mutate: uploadPhoto, isPending } = useMutation({
  mutationFn: async ({ uri, userId }) => {
    const formData = new FormData();
    formData.append('photo', {
      uri,
      type: 'image/jpeg',
      name: 'photo.jpg',
    });
    formData.append('userId', userId);

    const response = await fetch('/api/photos', {
      method: 'POST',
      body: formData,
      // Do NOT set Content-Type — let fetch set multipart boundary
    });
    return response.json();
  },
  onSuccess: () => queryClient.invalidateQueries({ queryKey: ['photos'] }),
});

Chaining Mutations

For workflows that require multiple sequential server operations (e.g., create order then process payment), chain mutations using mutateAsync in sequence. Each mutation can use the result of the previous one as its input. Wrap the chain in try/catch for error handling.

If intermediate steps fail, you may need to roll back earlier steps manually or trigger compensating transactions. Design your API for atomicity where possible to simplify client-side error recovery.

const { mutateAsync: createOrder } = useMutation({ mutationFn: apiCreateOrder });
const { mutateAsync: processPayment } = useMutation({ mutationFn: apiProcessPayment });

async function handleCheckout(cartData, paymentInfo) {
  try {
    const order = await createOrder(cartData);
    const payment = await processPayment({ orderId: order.id, ...paymentInfo });
    queryClient.invalidateQueries({ queryKey: ['orders'] });
    navigation.navigate('OrderConfirmation', { orderId: order.id });
  } catch (error) {
    Alert.alert('Checkout failed', error.message);
  }
}

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: useMutation handles write operations (POST/PUT/DELETE) with isPending, isError, and isSuccess state, onSuccess is the primary place to call queryClient.invalidateQueries to refresh related cached data, and optimistic updates via onMutate immediately update the cache with a rollback snapshot for if the server request fails. Next up we persist the React Query cache to AsyncStorage for offline-first behavior.

คำถามที่พบบ่อย

บทเรียน “การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส React Native Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส React Native Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง”

ใช้ useMutation เพื่อส่งข้อมูลด้วย POST ทำให้คำค้นที่เกี่ยวข้องไม่ถูกต้องเมื่อสำเร็จเพื่อให้รายการรีเฟรชโดยอัตโนมัติ และแสดงผลตอบสนองของ UI เชิงคาดการณ์ขณะที่คำขอกำลังทำงาน คุณปฏิบัติ React Native Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน React Native Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน React Native Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน React Native Academy นี้ได้ไหม

ได้ บทเรียน React Native Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. QueryClient, QueryClientProvider และ useQuery
  2. การเปลี่ยนแปลงด้วย useMutation และการทำให้แคชไม่ถูกต้อง
  3. การคงแคชคำค้นด้วย AsyncStorage
  4. การดึงข้อมูลเบื้องหลังและการกำหนดเวลาข้อมูลเก่า
← กลับไปที่ React Native Academy