> ## Documentation Index
> Fetch the complete documentation index at: https://inertiajs-vi.tuantq.online/llms.txt
> Use this file to discover all available pages before exploring further.

# Optimistic Updates

export const VueSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return "Vue";
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || "Vue";
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (code !== "Vue") {
    return null;
  }
  return children;
};

export const SvelteSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return null;
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || null;
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (!code?.includes("Svelte")) {
    return null;
  }
  return children;
};

export const ReactSpecific = ({children}) => {
  const [code, setCode] = useState(() => {
    if (typeof window === "undefined") {
      return null;
    }
    return localStorage.getItem("code")?.replace(/"/g, "") || null;
  });
  useEffect(() => {
    const handler = event => {
      if (event.detail?.key === "code") {
        setCode(event.detail.value?.replace(/"/g, ""));
      }
    };
    window.addEventListener("localStorageUpdate", handler);
    return () => window.removeEventListener("localStorageUpdate", handler);
  }, []);
  if (code !== "React") {
    return null;
  }
  return children;
};

export const ClientSpecific = ({children}) => {
  const [nada, setNada] = useState();
  return children;
};

Inertia cho phép cập nhật UI ngay lập tức mà không chờ server phản hồi, chẳng hạn tăng bộ đếm like, bật/tắt bookmark hoặc thêm phần tử vào danh sách. Optimistic update áp dụng thay đổi tức thì trong lúc request đang chạy và tự động rollback nếu request thất bại.

## Router visits

Bạn có thể nối phương thức `optimistic()` trước bất kỳ router visit nào. Callback nhận page props hiện tại và nên trả partial update cần áp dụng ngay.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import { router } from '@inertiajs/vue3'

  router.optimistic((props) => ({
      post: {
          ...props.post,
          likes: props.post.likes + 1,
      },
  })).post(`/posts/${post.id}/like`)
  ```

  ```js React icon="react" theme={null}
  import { router } from '@inertiajs/react'

  router.optimistic((props) => ({
      post: {
          ...props.post,
          likes: props.post.likes + 1,
      },
  })).post(`/posts/${post.id}/like`)
  ```

  ```js Svelte icon="s" theme={null}
  import { router } from '@inertiajs/svelte'

  router.optimistic((props) => ({
      post: {
          ...props.post,
          likes: props.post.likes + 1,
      },
  })).post(`/posts/${post.id}/like`)
  ```
</CodeGroup>

Optimistic update được áp dụng ngay vào props của page hiện tại, vì vậy component re-render với giá trị mới trước khi request được gửi. Khi server phản hồi, Inertia thay dữ liệu optimistic bằng response từ server. Nếu request thất bại, prop tự động được khôi phục về giá trị ban đầu.

## Component Form

Component `<Form>` hỗ trợ optimistic update qua prop `optimistic`. Vì component quản lý input data nội bộ, form data được cung cấp làm đối số thứ hai của callback.

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <template>
    <Form
      action="/todos"
      method="post"
      :optimistic="(props, data) => ({
        todos: [...props.todos, { id: Date.now(), name: data.name, done: false }],
      })"
    >
      <input type="text" name="name" />
      <button type="submit">Add Todo</button>
    </Form>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  <Form
    action="/todos"
    method="post"
    optimistic={(props, data) => ({
      todos: [...props.todos, { id: Date.now(), name: data.name, done: false }],
    })}
  >
    <input type="text" name="name" />
    <button type="submit">Add Todo</button>
  </Form>
  ```

  ```svelte Svelte icon="s" theme={null}
  <Form
      action="/todos"
      method="post"
      optimistic={(props, data) => ({
          todos: [...props.todos, { id: Date.now(), name: data.name, done: false }],
      })}
  >
      <input type="text" name="name" />
      <button type="submit">Add Todo</button>
  </Form>
  ```
</CodeGroup>

## Form helper

Helper `useForm` cũng hỗ trợ optimistic update qua cùng phương thức `optimistic()`.

<CodeGroup>
  ```vue Vue icon="vuejs" theme={null}
  <script setup>
  import { useForm } from '@inertiajs/vue3'

  const props = defineProps({ posts: Array })

  const form = useForm({
      title: '',
  })

  function save() {
      form.optimistic((props) => ({
          posts: [...props.posts, { title: form.title }],
      })).post('/posts')
  }
  </script>

  <template>
      <input v-model="form.title" />
      <button @click="save" :disabled="form.processing">Save</button>
  </template>
  ```

  ```jsx React icon="react" theme={null}
  import { useForm } from '@inertiajs/react'

  export default function Posts({ posts }) {
      const { data, setData, optimistic, post, processing } = useForm({
          title: '',
      })

      function save(e) {
          e.preventDefault()
          optimistic((props) => ({
              posts: [...props.posts, { title: data.title }],
          }))
          post('/posts')
      }

      return (
          <form onSubmit={save}>
              <input value={data.title} onChange={e => setData('title', e.target.value)} />
              <button type="submit" disabled={processing}>Save</button>
          </form>
      )
  }
  ```

  ```svelte Svelte icon="s" theme={null}
  <script>
  import { useForm } from '@inertiajs/svelte'

  let { posts } = $props()

  const form = useForm({
      title: '',
  })

  function save() {
      form.optimistic((props) => ({
          posts: [...props.posts, { title: form.title }],
      })).post('/posts')
  }
  </script>

  <input bind:value={form.title} />
  <button onclick={save} disabled={form.processing}>Save</button>
  ```
</CodeGroup>

## HTTP requests

Hook [`useHttp`](/v3/the-basics/http-requests) cũng hỗ trợ optimistic update. Vì HTTP request không tương tác với page props của Inertia, optimistic callback nhận và cập nhật dữ liệu riêng của form. Khi thất bại, form data được khôi phục về state trước request.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import { useHttp } from '@inertiajs/vue3'

  const form = useHttp({
      likes: 0,
  })

  form.optimistic((data) => ({
      likes: data.likes + 1,
  })).post('/api/likes')
  ```

  ```js React icon="react" theme={null}
  import { useHttp } from '@inertiajs/react'

  const { optimistic, post } = useHttp({
      likes: 0,
  })

  optimistic((data) => ({
      likes: data.likes + 1,
  }))
  post('/api/likes')
  ```

  ```js Svelte icon="s" theme={null}
  import { useHttp } from '@inertiajs/svelte'

  const form = useHttp({
      likes: 0,
  })

  form.optimistic((data) => ({
      likes: data.likes + 1,
  })).post('/api/likes')
  ```
</CodeGroup>

## Cách hoạt động

Khi optimistic update được áp dụng:

1. Prop được trả về được so sánh với page prop hiện tại và chỉ những key thực sự thay đổi mới được snapshot
2. Giá trị callback trả về được merge vào dữ liệu hiện tại
3. Request được gửi tới server
4. Khi thành công, response của server thay thế dữ liệu optimistic
5. Khi thất bại, chỉ những key đã snapshot được khôi phục, rollback các thay đổi optimistic

Callback nên trả về object **partial** chỉ chứa các key bạn muốn cập nhật. Các giá trị trả về được shallow-merge với dữ liệu hiện tại.

### Tự động rollback

Optimistic state tự động được khôi phục trong một số trường hợp:

* **Validation error (422)**: Optimistic state được khôi phục và validation error được giữ lại
* **Server error**: Khi request thất bại vì bất kỳ lý do nào khác, state ban đầu được khôi phục
* **Visit bị gián đoạn**: Khi visit mới ngắt request đang chạy, optimistic state trước đó được khôi phục trước khi optimistic update mới được áp dụng

### Cập nhật đồng thời

Nhiều optimistic request có thể chạy cùng lúc. Inertia theo dõi prop nào được mỗi optimistic update chạm tới, và server response sẽ không ghi đè một prop cho tới khi optimistic request cuối cùng sửa prop đó đã resolve.

## Tùy chọn inline

Bạn cũng có thể truyền optimistic callback trực tiếp trong visit options thay vì nối method.

<CodeGroup>
  ```js Vue icon="vuejs" theme={null}
  import { router } from '@inertiajs/vue3'

  router.post(`/posts/${post.id}/like`, {}, {
      optimistic: (props) => ({
          post: { ...props.post, likes: props.post.likes + 1 },
      }),
  })
  ```

  ```js React icon="react" theme={null}
  import { router } from '@inertiajs/react'

  router.post(`/posts/${post.id}/like`, {}, {
      optimistic: (props) => ({
          post: { ...props.post, likes: props.post.likes + 1 },
      }),
  })
  ```

  ```js Svelte icon="s" theme={null}
  import { router } from '@inertiajs/svelte'

  router.post(`/posts/${post.id}/like`, {}, {
      optimistic: (props) => ({
          post: { ...props.post, likes: props.post.likes + 1 },
      }),
  })
  ```
</CodeGroup>

Tùy chọn inline cũng hoạt động với các phương thức submit của `useHttp`.

```js theme={null}
form.post('/api/likes', {
    optimistic: (data) => ({
        likes: data.likes + 1,
    }),
})
```

***

## Tài liệu chính thức

Bài dịch này được đối chiếu từ [tài liệu Inertia.js v3 chính thức](https://inertiajs.com/docs/v3/the-basics/optimistic-updates). Nếu có khác biệt do phiên bản hoặc cập nhật mới, hãy ưu tiên tài liệu chính thức làm nguồn tham chiếu.
