Nếu bạn từng xây dựng một form liên hệ và chứng kiến toàn bộ trang web tải lại sau khi người dùng nhấn "Gửi", bạn đã biết trải nghiệm đó khó chịu như thế nào. Sử dụng kỹ thuật gửi form HTML bằng fetch javascript, bạn có thể gửi dữ liệu form đến backend endpoint ở chế độ nền, giữ người dùng ở cùng một trang, và hiển thị thông báo thành công hoặc lỗi rõ ràng mà không cần tải lại toàn bộ trang. Hướng dẫn này sẽ đưa bạn qua từng bước, từ việc viết HTML cơ bản đến việc trỏ lệnh gọi fetch() của bạn đến endpoint Sendform để các submission được gửi trực tiếp đến hộp thư hoặc quy trình làm việc được kết nối của bạn.
Điểm chính:
- Fetch API cho phép bạn gửi dữ liệu form mà không cần tải lại trang, mang lại trải nghiệm mượt mà hơn cho người dùng.
- Bắt sự kiện
submitvà gọipreventDefault()là nền tảng của bất kỳ pattern gửi form ajax nào. - Sendform cung cấp URL endpoint sẵn sàng, vì vậy bạn không cần viết code backend để nhận và lưu trữ submission.
- Xử lý phản hồi đúng cách (hiển thị phản hồi thành công hoặc lỗi) cũng quan trọng như việc gửi dữ liệu chính xác.
Mục lục
Tại Sao Dùng fetch() Thay Vì Submit Form Mặc Định
Hành vi submit form mặc định của trình duyệt chỉ làm một việc: nó serialize các trường form, gửi request POST (hoặc GET) đến URL action, và sau đó tải bất kỳ phản hồi nào mà server trả về. Điều đó có nghĩa là người dùng sẽ thấy màn hình trắng nhấp nháy, mất vị trí cuộn, và phải chờ trang mới được vẽ. Trên kết nối chậm, điều này có thể cảm thấy như bị lỗi.
Fetch API giải quyết vấn đề này bằng cách thực hiện HTTP requests theo chương trình, hoàn toàn trong JavaScript, mà không cần điều hướng đi nơi khác. Điều này cho phép pattern gửi form không tải lại trang giữ người dùng tương tác và cho phép bạn kiểm soát mọi khía cạnh của trải nghiệm người dùng, bao gồm trạng thái loading, phản hồi validation inline, và banner thành công có hoạt ảnh.
Những lý do thực tế bổ sung để ưu tiên fetch():
- Bạn có thể đính kèm custom headers (ví dụ: CSRF token hoặc authorization key) mà HTML form thông thường không thể gửi.
- Bạn có thể serialize dữ liệu thành JSON,
FormData, hoặc URL-encoded strings tùy thuộc vào những gì endpoint mong đợi. - Xử lý lỗi rõ ràng. Bạn quyết định "thất bại" có nghĩa là gì và cách truyền đạt nó.
- Nó hoạt động trên bất kỳ static site nào, bao gồm những site được host trên GitHub Pages, Netlify, hoặc CDN, vì logic hoàn toàn nằm trong trình duyệt.
Lưu ý: Nếu bạn đang làm việc với website builder như Webflow, WordPress, hoặc Hugo, cách tiếp cận fetch() tương tự vẫn áp dụng. Xem hướng dẫn của chúng tôi về cách tích hợp Sendform với website builder của bạn để biết tips cụ thể cho từng nền tảng.
Thiết Lập HTML Form Cơ Bản
Trước khi viết một dòng JavaScript nào, bạn cần một HTML form có cấu trúc tốt. Chi tiết quan trọng ở đây là bạn không cần thuộc tính action trỏ đến đâu cả, vì JavaScript sẽ xử lý việc submit. Tuy nhiên, bạn muốn có thuộc tính name có ý nghĩa trên mọi input - chúng trở thành field keys trong payload được submit.
<form id="contact-form" novalidate>
<div>
<label for="name">Tên của bạn</label>
<input type="text" id="name" name="name" required placeholder="Nguyễn Văn A">
</div>
<div>
<label for="email">Địa chỉ Email</label>
<input type="email" id="email" name="email" required placeholder="[email protected]">
</div>
<div>
<label for="message">Tin nhắn</label>
<textarea id="message" name="message" rows="5" required></textarea>
</div>
<button type="submit">Gửi tin nhắn</button>
<!-- Khu vực phản hồi -->
<div id="form-feedback" aria-live="polite"></div>
</form>Một số điều đáng lưu ý trong markup này:
novalidatetrên form element vô hiệu hóa validation bubbles của trình duyệt gốc, cho phép bạn kiểm soát hoàn toàn thông báo lỗi trong JavaScript.- Div
id="form-feedback"vớiaria-live="polite"là nơi thông báo thành công và lỗi sẽ xuất hiện. Thuộc tính ARIA đảm bảo screen readers tự động thông báo phản hồi. - Mọi input đều có cả
id(cho liên kết label) vàname(cho form payload).
Bắt Sự Kiện Submit
Bước đầu tiên trong bất kỳ gửi form javascript nào là chặn hành vi mặc định của trình duyệt. Bạn làm điều này bằng cách lắng nghe sự kiện submit trên form element và ngay lập tức gọi event.preventDefault().
const form = document.getElementById('contact-form');
form.addEventListener('submit', async function (event) {
event.preventDefault(); // Ngừng điều hướng trang mặc định
// Validation cơ bản phía client
const name = form.elements['name'].value.trim();
const email = form.elements['email'].value.trim();
const message = form.elements['message'].value.trim();
if (!name || !email || !message) {
showFeedback('Vui lòng điền vào tất cả các trường.', 'error');
return;
}
// Tiến hành gửi dữ liệu (phần tiếp theo)
await submitForm({ name, email, message });
});Bằng cách tách logic validation khỏi network call, bạn giữ code dễ đọc và dễ mở rộng. Từ khóa async trên event handler cho phép bạn sử dụng await bên trong nó, làm cho Fetch API form data call trông đồng bộ và tránh promise chains lồng nhau sâu.
Trỏ fetch() Đến Sendform Endpoint
Đây là nơi công việc thực sự diễn ra. Thay vì xây dựng server riêng để nhận, lưu trữ và chuyển tiếp form submissions, bạn có thể sử dụng Sendform làm backend của mình. Sau khi tạo form trong dashboard Sendform, bạn sẽ nhận được URL endpoint duy nhất. URL đó là tất cả những gì bạn cần.
Hàm submission bên dưới sử dụng FormData API để xây dựng payload, mà Sendform chấp nhận nguyên bản:
async function submitForm(data) {
// Thay thế URL này bằng Sendform endpoint thực tế của bạn
const SENDFORM_ENDPOINT = 'https://sendform.net/vi/YOUR_FORM_ID';
const formData = new FormData();
formData.append('name', data.name);
formData.append('email', data.email);
formData.append('message', data.message);
try {
const response = await fetch(SENDFORM_ENDPOINT, {
method: 'POST',
body: formData,
});
if (response.ok) {
showFeedback('Cảm ơn! Tin nhắn của bạn đã được gửi.', 'success');
form.reset();
} else {
const errorData = await response.json().catch(() => ({}));
const errorMsg = errorData.message || 'Có lỗi xảy ra. Vui lòng thử lại.';
showFeedback(errorMsg, 'error');
}
} catch (networkError) {
showFeedback('Lỗi mạng. Kiểm tra kết nối và thử lại.', 'error');
}
}Những quyết định chính được đưa ra trong code này:
- Không có header
Content-Typenào được set thủ công. Khi bạn truyền objectFormDatalàm body, trình duyệt tự động set boundarymultipart/form-datachính xác. - Kiểm tra
response.okbao gồm tất cả HTTP status codes 2xx, không chỉ 200. Điều này mạnh mẽ hơn so với việc so sánhresponse.status === 200. try/catchbên ngoài bắt các lỗi cấp mạng (lỗi DNS, trạng thái offline) màresponse.okkhông bao giờ thấy được.
Xử Lý Phản Hồi - Thông Báo Thành Công và Lỗi
Gửi dữ liệu chỉ là một nửa công việc. Người dùng cần phản hồi ngay lập tức và rõ ràng. Hàm helper showFeedback() được tham chiếu ở trên viết thông báo vào feedback div mà bạn đã thêm vào HTML:
function showFeedback(message, type) {
const feedbackEl = document.getElementById('form-feedback');
feedbackEl.textContent = message;
feedbackEl.className = type === 'success' ? 'feedback-success' : 'feedback-error';
}Điều này cố tình tối giản. Trong dự án thực tế, bạn có thể thay textContent bằng component có hoạt ảnh hoặc toast notification library, nhưng pattern vẫn giữ nguyên: cập nhật DOM dựa trên kết quả của lệnh gọi fetch().
Đối với các tình huống nâng cao hơn, chẳng hạn như chuyển hướng đến trang cảm ơn tùy chỉnh hoặc kích hoạt automation downstream sau khi submit, hãy xem bài viết của chúng tôi về cách tự động hóa quy trình làm việc form với webhooks, Zapier, và APIs.
Best Practices và Tips
Làm cho code hoạt động là một chuyện. Triển khai nó theo cách có thể duy trì trong production là chuyện khác. Dưới đây là những tips quan trọng nhất cần ghi nhớ:
- Vô hiệu hóa nút submit trong quá trình request. Set
button.disabled = truetrước khi gọifetch()và kích hoạt lại trong blockfinally. Điều này ngăn chặn submissions trùng lặp nếu người dùng nhấp nhiều lần. - Hiển thị trạng thái loading. Thay đổi text nút thành "Đang gửi..." hoặc thêm spinner class khi request đang diễn ra. Người dùng không thấy phản hồi thường nghĩ không có gì xảy ra và nhấp lại.
- Validation cả phía server. Validation phía client là để trải nghiệm người dùng. Sendform và bất kỳ backend service nào nên coi tất cả dữ liệu đến là không đáng tin cậy.
- Sử dụng HTTPS ở mọi nơi. Gửi dữ liệu form qua HTTP thông thường làm lộ input của người dùng trong quá trình truyền. Sendform endpoints mặc định là HTTPS, nhưng hãy đảm bảo trang của bạn cũng được phục vụ qua HTTPS.
- Thêm bảo vệ spam. Trường honeypot hoặc tích hợp CAPTCHA giảm đáng kể submissions rác. Để tìm hiểu sâu hơn về chủ đề này, xem hướng dẫn của chúng tôi về best practices bảo vệ spam cho forms.
- Test error path một cách có chủ ý. Tạm thời thay đổi endpoint URL thành thứ gì đó không hợp lệ và xác nhận thông báo lỗi của bạn xuất hiện. Hầu hết developers chỉ test happy path.
- Giữ endpoint URL ra khỏi version control. Nếu dự án của bạn là open source, lưu trữ Sendform endpoint trong biến môi trường hoặc config file được liệt kê trong
.gitignore.
Người dùng static site: Nếu dự án của bạn là Hugo, Eleventy, hoặc plain HTML site không có server, cách tiếp cận fetch() được mô tả ở đây là phương pháp được khuyến nghị. Đọc thêm trong hướng dẫn của chúng tôi về xử lý form serverless cho static sites.
Kết Luận
Thay thế form submission mặc định bằng lệnh gọi fetch() là một trong những cải tiến tác động cao nhất mà bạn có thể thực hiện đối với bất kỳ contact hoặc lead-capture form nào. Kết quả là trải nghiệm nhanh hơn, chuyên nghiệp hơn giữ người dùng trên trang của bạn và cho phép bạn kiểm soát hoàn toàn thông báo phản hồi. Kết hợp điều đó với Sendform endpoint và bạn loại bỏ hoàn toàn nhu cầu về bất kỳ server-side code nào. Form của bạn đã live, submissions của bạn đến hộp thư, và người dùng không bao giờ thấy tải lại trang khó chịu. Tạo Sendform endpoint miễn phí của bạn ngay hôm nay và nhận submission đầu tiên trong vài phút.
Câu Hỏi Thường Gặp
Có. Vì fetch() chạy hoàn toàn trong trình duyệt, nó hoạt động trên static HTML sites, JAMstack projects, và bất kỳ nền tảng nào phục vụ HTML. Bạn không cần server riêng. Yêu cầu duy nhất là endpoint (như Sendform URL) có thể nhận POST request.
fetch() là sự thay thế hiện đại cho XMLHttpRequest. Nó sử dụng Promises, hỗ trợ async/await, và có API sạch hơn. Đối với dự án mới, fetch() luôn được ưu tiên. Cả hai đều đạt được kết quả ajax form submit giống nhau, nhưng fetch() yêu cầu ít boilerplate code hơn đáng kể.
Không. Khi bạn truyền object FormData làm body, trình duyệt tự động set Content-Type thành multipart/form-data và bao gồm boundary string chính xác. Setting thủ công thực sự sẽ phá vỡ request bằng cách bỏ qua giá trị boundary đó.
Đăng ký tại Sendform, tạo form mới trong dashboard, và copy URL endpoint được tạo. Dán URL đó làm target trong lệnh gọi fetch() của bạn. Submissions sẽ được chuyển tiếp đến địa chỉ email được cấu hình của bạn ngay lập tức.
Lỗi mạng khiến fetch() Promise bị reject, được bắt bởi block try/catch bên ngoài trong code ví dụ. Người dùng thấy thông báo "Lỗi mạng" mà bạn đã định nghĩa. Submission không được xếp hàng tự động; người dùng phải thử lại sau khi kết nối được khôi phục.