Đang tải...

Ếch Trendy

Cùng chú ếch nhỏ khám phá thế giới Web đầy biến động. Cập nhật trending nhanh như cách ếch đớp mồi! ⌨️🌿


0

React 19 có gì mới? Những thay đổi dev frontend cần biết trong 2026

Ếch Trendy
Ếch Trendy

16 giờ trước · 7 phút đọc

Mình từng viết 40 dòng chỉ để handle một cái form

Hồi React 18, mỗi lần viết form submit là mình lại copy-paste cái pattern quen thuộc: useState cho loading, useState cho error, preventDefault, try/catch, finally set loading về false. Nghe quen không?

React 19 ra đời để giải quyết đúng cái vòng lặp đó. Bản stable phát hành cuối 2024, và đến 2026 đây là nền tảng chính cho bất kỳ dự án React mới nào. Trọng tâm không phải là thay đổi cách render nội bộ, mà là giảm boilerplate thực tế mà mình viết mỗi ngày.

Bài này mình sẽ đi qua từng thay đổi với code trước/sau thật cụ thể, giải thích khi nào nên dùng, và cuối bài có migration guide để upgrade từ React 18 mà không làm vỡ app.

347282 React 19 tập trung vào developer experience, không chỉ tối ưu render


Actions: viết form async không còn cần boilerplate

Đây là thay đổi mình thích nhất trong React 19. Thay vì tự quản lý loading state và gọi preventDefault, bạn truyền thẳng một async function vào form action. React lo phần còn lại.

Trước React 19:

function SignupForm() {
  const [loading, setLoading] = useState(false);

  async function handleSubmit(e) {
    e.preventDefault();
    setLoading(true);
    try {
      await signup(new FormData(e.currentTarget));
    } finally {
      // Nhớ set về false dù thành công hay lỗi
      setLoading(false);
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <button disabled={loading}>Submit</button>
    </form>
  );
}

React 19:

function SignupForm() {
  async function signupAction(formData) {
    // FormData tự động, không cần e.currentTarget
    await signup(formData);
  }

  return (
    <form action={signupAction}>
      <button>Submit</button>
    </form>
  );
}

347283 Ít state hơn, ít lỗi hơn - đặc biệt khi quên set loading về false

Code React 19 ngắn hơn không chỉ vì ít dòng - mà vì bạn không còn tự quản lý một đống state có thể bị out-of-sync. Khi action throw error, React tự xử lý. Khi form reset sau submit, React cũng lo.

Dùng Actions khi nào? Form đăng nhập/đăng ký, CRUD admin, checkout, comment, profile update - bất kỳ form nào có async submit và cần pending state rõ ràng.


useActionState và useFormStatus: quản lý state submit sạch hơn

Hai hook này thường đi kèm nhau. useActionState gom toàn bộ submit flow vào một chỗ, còn useFormStatus cho phép component con đọc trạng thái form mà không cần prop drilling.

useActionState

Thay vì nhiều useState rời rạc quanh một form, bạn có một hook duy nhất:

function LoginForm() {
  const [state, formAction, isPending] = useActionState(
    async (prevState, formData) => {
      // Trả về state mới - có thể là error hoặc success data
      return await login(formData);
    },
    null // initialState
  );

  return (
    <form action={formAction}>
      <input name="email" type="email" />
      <input name="password" type="password" />
      <button disabled={isPending}>
        {isPending ? "Đang đăng nhập..." : "Đăng nhập"}
      </button>
      {/* Hiển thị lỗi từ server nếu có */}
      {state?.error && <p className="error">{state.error}</p>}
    </form>
  );
}

State, action, pending flag - tất cả từ một hook. Khi form submit thành công hay thất bại, state được cập nhật tự động.

useFormStatus

Vấn đề cũ: nút Submit nằm trong component con, nhưng không biết form cha đang pending hay không nếu không truyền props thủ công.

import { useFormStatus } from "react-dom";

// Component tái sử dụng được - không cần biết form nào bao nó
function SubmitButton({ label = "Lưu" }) {
  const { pending } = useFormStatus();
  return (
    <button disabled={pending}>
      {pending ? "Đang lưu..." : label}
    </button>
  );
}

// Dùng trong bất kỳ form nào
function ProfileForm() {
  return (
    <form action={updateProfile}>
      <input name="name" />
      <SubmitButton label="Cập nhật hồ sơ" />
    </form>
  );
}

347284 useFormStatus đọc context form cha - không cần truyền props từng cấp

Lợi ích thực tế: bạn xây một SubmitButton component dùng chung cho toàn app. Nó tự biết khi nào disable, khi nào show spinner - không cần ai nhớ truyền loading prop.

Một điểm cần lưu ý: useFormStatus chỉ đọc được trạng thái của form cha gần nhất trong DOM tree. Nếu SubmitButton không nằm trong <form>, hook sẽ trả về { pending: false } mà không báo lỗi.


useOptimistic: UI phản hồi trước khi server xác nhận

Optimistic UI là kỹ thuật update giao diện ngay lập tức, rồi reconcile với dữ liệu thật sau khi server trả về. Trước React 19, bạn phải tự quản lý temporary state, rollback khi lỗi - khá cồng kềnh.

useOptimistic giải quyết pattern này:

function LikeButton({ postId, initialLikes }) {
  const [likes, setLikes] = useState(initialLikes);
  const [optimisticLikes, addOptimisticLike] = useOptimistic(
    likes,
    (currentLikes, increment) => currentLikes + increment
  );

  async function handleLike() {
    // Cập nhật UI ngay lập tức (+1)
    addOptimisticLike(1);
    try {
      const result = await likePost(postId);
      // Sync với dữ liệu thật từ server
      setLikes(result.totalLikes);
    } catch {
      // React tự rollback về giá trị trước nếu lỗi
    }
  }

  return (
    <button onClick={handleLike}>
      ❤️ {optimisticLikes}
    </button>
  );
}

347285 Người dùng thấy kết quả ngay, không cần chờ server round-trip

Dùng khi nào? Like, reaction, add to cart, reorder danh sách, đánh dấu đã đọc - bất kỳ action nào người dùng expect kết quả ngay và rollback được nếu server fail.

Trade-off cần nhớ: nếu server fail thường xuyên, optimistic UI có thể gây confuse hơn là helpful. Hook này phù hợp khi tỷ lệ thành công cao và rollback ít xảy ra.


ref như một prop: bỏ forwardRef cho component mới

Trước React 19, mỗi khi component function cần expose DOM node ra ngoài (để focus, measure, scroll...), bạn bắt buộc phải bọc bằng forwardRef. Ceremony thừa, đặc biệt khi xây design system với hàng chục wrapper component.

Trước React 19:

// Phải dùng forwardRef mỗi khi muốn pass ref
const MyInput = forwardRef(function MyInput(props, ref) {
  return <input {...props} ref={ref} />;
});

// Caller
const inputRef = useRef(null);
<MyInput ref={inputRef} placeholder="Email" />

React 19:

// ref là prop bình thường, không cần forwardRef
function MyInput({ ref, ...props }) {
  return <input {...props} ref={ref} />;
}

// Caller vẫn dùng bình thường
const inputRef = useRef(null);
<MyInput ref={inputRef} placeholder="Email" />

347286 forwardRef vẫn chạy, nhưng code mới không cần dùng nữa

Lợi ích rõ nhất khi xây design system: Button, Input, Modal, Tooltip - tất cả cần expose ref ra ngoài để parent có thể focus/measure. Với cách cũ, từng component phải bọc forwardRef. Với React 19, đơn giản như nhận prop thường.

Lưu ý quan trọng: forwardRef vẫn hoạt động bình thường trong React 19, không bị deprecate đột ngột. Codebase cũ không cần migrate ngay - chỉ áp dụng cách mới cho component viết mới.


Server Components: render trên server, bundle nhẹ hơn

Server Components không phải khái niệm hoàn toàn mới, nhưng React 19 tiếp tục củng cố và mở rộng - đặc biệt ở các bản 2025-2026 với Context trong Server Components.

Ý tưởng cốt lõi: một phần UI render hoàn toàn trên server, không gửi JS component xuống client. Người dùng nhận HTML tĩnh cho phần đó, client chỉ nhận JS cho phần thật sự cần tương tác.

// Đây là Server Component - render trên server, không có JS bundle client
async function ProductList({ categoryId }) {
  // Gọi thẳng database, không cần API route
  const products = await db.products.findMany({ categoryId });

  return (
    <ul>
      {products.map(p => (
        <li key={p.id}>
          {p.name} - {p.price}đ
          {/* Chỉ nút Add to cart cần client interactivity */}
          <AddToCartButton productId={p.id} />
        </li>
      ))}
    </ul>
  );
}

// 'use client' - chỉ phần này mới là JS gửi xuống browser
'use client';
function AddToCartButton({ productId }) {
  return <button onClick={() => addToCart(productId)}>Thêm vào giỏ</button>;
}

347287 Chỉ component cần interactivity mới gửi JS xuống client

Khi nào dùng? Trang nội dung nhiều dữ liệu (dashboard, listing page, blog), phần không cần click/hover/input. Khi không dùng Server Components, toàn bộ data fetching logic vẫn chạy client-side - không sai, nhưng bundle to hơn và TTFB chậm hơn.

Nếu bạn đang dùng Next.js App Router, Server Components là mặc định - bạn đã xài rồi mà có thể chưa để ý.