React 19 có gì mới

React 19 tập trung chuẩn hóa luồng mutation với Actions + hooks cho form/optimistic, cải thiện quản lý head/tài nguyên để tối ưu hiệu năng.

React 19 tập trung chuẩn hóa luồng mutation với Actions + hooks cho form/optimistic, cải thiện quản lý head/tài nguyên để tối ưu hiệu năng.



1. useActionState

👉 Quản lý async action (submit/mutation) và tự động có:

  • state (kết quả trả về)

  • pending (đang submit hay không)

Không cần tự viết useState + try/catch + loading.

Điều “magic” của React 19 ✨

  • Không cần onSubmit

  • Không cần preventDefault

  • Không cần useState(loading)

  • React tự quản lý async flow + pending

Ví dụ:

import { useActionState } from "react";

async function loginAction(prevState: { error?: string }, formData: FormData) {
  const email = formData.get("email");

  await new Promise((r) => setTimeout(r, 1000)); // giả lập API

  if (email !== "admin@gmail.com") {
    return { error: "Invalid email" };
  }

  return { error: undefined };
}

export default function LoginForm() {
  const [state, formAction, isPending] = useActionState(loginAction, {
    error: undefined,
  });

  return (
    <form action={formAction}>
      <input name="email" placeholder="Email" />
      <button disabled={isPending}>
        {isPending ? "Logging in..." : "Login"}
      </button>

      {state.error && <p style={{ color: "red" }}>{state.error}</p>}
    </form>
  );
}


2.useEffectEvent

useEffectEvent (React 19) dùng để tạo event handler luôn đọc được state/props mới nhất nhưng không làm thay đổi dependency của useEffect → giải quyết triệt để bài toán stale closure.

Ví dụ:

Cách 1: dùng useRef để luôn đọc giá trị mới

import { useEffect, useRef, useState } from "react"; export default function Counter() { const [count, setCount] = useState(0); const countRef = useRef(count); useEffect(() => { countRef.current = count; }, [count]); const logAfter3s = () => { setTimeout(() => { console.log("count after 3s =", countRef.current); // ✅ always latest }, 3000); }; return ( <div> <div>Count: {count}</div> <button onClick={() => setCount((c) => c + 1)}>+1</button> <button onClick={logAfter3s}>Log after 3s</button> </div> ); }

Cách 2 (React 19): useEffectEvent

import { useState, useEffectEvent } from "react"; export default function Counter() { const [count, setCount] = useState(0); const readLatestCount = useEffectEvent(() => count); const logAfter3s = () => { setTimeout(() => { console.log("count after 3s =", readLatestCount()); // ✅ latest }, 3000); }; return ( <div> <div>Count: {count}</div> <button onClick={() => setCount((c) => c + 1)}>+1</button> <button onClick={logAfter3s}>Log after 3s</button> </div> ); }

Nếu bạn muốn mình giải thích theo “cơ chế render → tạo function mới → closure capture giá trị” bằng một flow ngắn 4 bước để dễ nhớ khi phỏng vấn, mình viết luôn.


3. useDebugValue

Là hook dùng để hiển thị thông tin debug cho custom hook trong React DevTools, giúp bạn hiểu trạng thái bên trong hook khi inspect component.

  • Chỉ dùng trong custom hook

  • Chỉ ảnh hưởng DevTools, không ảnh hưởng UI

  • Giúp debug:

    • loading / error

    • trạng thái kết nối

    • auth status

    • cache state…

ví dụ:

4️⃣ Ví dụ thực tế hay gặp (auth hook)

function useAuth() { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useDebugValue( { user, loading }, v => v.loading ? "Loading auth..." : v.user ? "Logged in" : "Logged out" ); return { user, loading }; }

Trong DevTools:

useAuth • Logged in


4. useOptimistic

cho phép UI phản hồi ngay khi user thao tác, không cần chờ API.

❌ Không optimistic

const addTodo = async () => { await api.addTodo() setTodos(await api.fetchTodos()) }
  • User click → chờ → UI mới đổi
    👉 UX chậm, cảm giác lag


2️⃣ useOptimistic hoạt động thế nào?

const [optimisticState, updateOptimistic] = useOptimistic(realState, reducer)
  • optimisticState: state để render UI

  • updateOptimistic: áp dụng thay đổi tạm thời

  • Khi action xong → React tự revert / sync


3️⃣ Ví dụ đơn giản nhất (Todo list)

import { useOptimistic, useState } from "react"; type Todo = { id: number; text: string }; export default function TodoApp() { const [todos, setTodos] = useState<Todo[]>([]); const [optimisticTodos, addOptimisticTodo] = useOptimistic( todos, (state, newTodo: Todo) => [...state, newTodo] ); const addTodo = async (text: string) => { const tempTodo = { id: Date.now(), text }; addOptimisticTodo(tempTodo); // ✅ update UI ngay try { await fakeApiAddTodo(text); // gọi server setTodos(prev => [...prev, tempTodo]); // sync thật } catch { // nếu fail → optimistic update tự bị discard } }; return ( <> <button onClick={() => addTodo("Learn React 19")}> Add todo </button> <ul> {optimisticTodos.map(t => ( <li key={t.id}>{t.text}</li> ))} </ul> </> ); } function fakeApiAddTodo(text: string) { return new Promise((res) => setTimeout(res, 1000)); }

4️⃣ Ví dụ chuẩn React 19: kết hợp với useActionState

const [todos, setTodos] = useState<Todo[]>([]); const [optimisticTodos, addOptimistic] = useOptimistic( todos, (state, todo: Todo) => [...state, todo] ); const [_, formAction] = useActionState( async (_, formData) => { const text = formData.get("text") as string; const todo = { id: Date.now(), text }; addOptimistic(todo); // UI update ngay await api.addTodo(text); // server setTodos(prev => [...prev, todo]); }, null );

5️⃣ Khi nào nên dùng useOptimistic?

  • Like / Unlike

  • Add comment

  • Toggle follow

  • Chat message

  • Form submit cần phản hồi nhanh


6️⃣ Khi KHÔNG nên dùng?

  • Action có thể fail cao

  • Logic rollback phức tạp

  • Dữ liệu phải chính xác tuyệt đối (banking, payment)


7️⃣ So sánh nhanh

HookMục đích
useStateCập nhật sau khi có data
useTransitionƯu tiên update
useActionStateQuản lý async action
useOptimisticUI lạc quan trước server
Comment