Triển khai Polling trong React và cách tạo 1 custom hook

Các tính năng yêu cầu xử lý các sự kiện theo thời gian thực (realtime) là 1 trong những tính năng phổ biến trong các ứng dụng ngày nay. Có nhiều cách để thực hiện được việc này như Websocket, Server sent events (SSE), … Nhân tiện hôm nay mình đang có 1 task mới yêu cầu realtime và có biết về 1 kỹ thuật nữa là HTTP Polling, trong quá trình research tìm thấy vài bài viết khá hay nên muốn viết 1 bài để tổng hợp lại cho bản thân cũng như anh em nào có nhu cầu tham khảo.

1. Giải thích về Polling

Polling là 1 kỹ thuật cho phép Client liên tục thực hiện việc truy xuất dữ liệu từ Server theo 1 chu kỳ nào đó (Ví dụ: 3s hoặc 5s sẽ gọi 1 lần) để kiểm tra xem dữ liệu có thay đổi chưa.

Các bước thực hiện kỹ thuật polling khá đơn giản:

  1. Khởi tạo yêu cầu lên server
  2. Chờ kết quả
  3. Sau khoảng thời gian chờ đã được thiết lập trước (VD: 5s), sẽ thực hiện 1 yêu cầu lên server nữa để lấy về dữ liệu mới nhất

Kỹ thuật này nghe qua khá cồng kềnh và tốn network, nhưng cách triển khai nó sẽ đơn giản và phù hợp với nhiều trình duyệt hơn các kỹ thuật realtime khác như websocket hay SSE (Server Sent Event). Cách này sẽ phù hợp với những tính năng không đòi hỏi sự realtime 1 cách tuyệt đối và có thể ngưng khi dữ liệu trả về đặt yêu cầu.

2. Triển khai Polling trong React

Trường hợp thực tế: Bạn đang làm 1 trang thanh toán cho website và bạn cần kiểm tra xem khách hàng đã thanh toán đơn hàng thành công chưa để xử lý những bước tiếp theo (Hiện thông báo, chuyển qua trang đơn hàng thành công)

Các bước thực hiện:

  • Dùng setInterval để thực hiện gọi API đều đặn (vd: sau mỗi 5s)
  • Kiểm tra kết quả trả về xem status của payment đã thành công chưa
  • Xử lý timeout: cleanup interval để tránh bị leak memory

Tham khảo đoạn code sau:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const PaymentStatus = ({ transactionId }) => {
  const [status, setStatus] = useState('pending'); // Initial status is 'pending'
  const [error, setError] = useState(null);
  const POLL_INTERVAL = 5000; // Poll every 5 seconds

  // Polling function to check payment status
  const checkPaymentStatus = async () => {
    try {
      const response = await axios.get(`/api/payment/status/${transactionId}`);
      const { status: paymentStatus } = response.data;

      setStatus(paymentStatus); // Update status based on API response

      // Stop polling if payment is either 'success' or 'failed'
      if (paymentStatus === 'success' || paymentStatus === 'failed') {
        clearInterval(pollingInterval); // Stop polling
      }
    } catch (err) {
      console.error('Error fetching payment status:', err);
      setError('Failed to retrieve payment status');
      clearInterval(pollingInterval); // Stop polling on error
    }
  };

  // Polling effect
  useEffect(() => {
    const pollingInterval = setInterval(() => {
      if (status === 'pending') {
        checkPaymentStatus(); // Keep polling if status is 'pending'
      }
    }, POLL_INTERVAL);

    // Cleanup function to stop polling when component unmounts
    return () => clearInterval(pollingInterval);
  }, [status]); // Re-run polling only when the status changes

  return (
    <div>
      <h2>Transaction Status: {status}</h2>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      {status === 'success' && <p>Payment was successful!</p>}
      {status === 'failed' && <p>Payment failed. Please try again.</p>}
    </div>
  );
};

export default PaymentStatus;

3. Cách tạo 1 Custom Hook về polling

Để tránh việc viết lại đoạn code dài dòng như trên mỗi khi cần triển khai polling, bạn có thể tạo 1 custom hook để thực hiện việc trên và dễ quản lý cũng như bảo trì

Tham khảo đoạn code sau:

// usePolling.ts
import { useEffect } from "react"
import { useRouter } from "next/navigation"

export function usePolling(func: any, ms: number) {]

    useEffect(() => {
        const intervalId = setInterval(() => {
           func()
        }, ms)

        return () => clearInterval(intervalId)
    }, [])
}

Cách sử dụng

import React, { useState, useEffect } from 'react';
import axios from 'axios';

const PaymentStatus = ({ transactionId }) => {
  const [status, setStatus] = useState('pending'); // Initial status is 'pending'
  const [error, setError] = useState(null);
  const POLL_INTERVAL = 5000; // Poll every 5 seconds

  // Polling function to check payment status
  const checkPaymentStatus = async () => {
    try {
      const response = await axios.get(`/api/payment/status/${transactionId}`);
      const { status: paymentStatus } = response.data;

      setStatus(paymentStatus); // Update status based on API response

      // Stop polling if payment is either 'success' or 'failed'
      if (paymentStatus === 'success' || paymentStatus === 'failed') {
        clearInterval(pollingInterval); // Stop polling
      }
    } catch (err) {
      console.error('Error fetching payment status:', err);
      setError('Failed to retrieve payment status');
      clearInterval(pollingInterval); // Stop polling on error
    }
  };
  usePolling(checkPaymentStatus, POLL_INTERVAL)

  return (
    <div>
      <h2>Transaction Status: {status}</h2>
      {error && <p style={{ color: 'red' }}>{error}</p>}
      {status === 'success' && <p>Payment was successful!</p>}
      {status === 'failed' && <p>Payment failed. Please try again.</p>}
    </div>
  );
};

export default PaymentStatus;

Referrence:

Leave a Reply

Discover more from Mai Trúc Quỳnh

Subscribe now to keep reading and get access to the full archive.

Continue reading