sw-fe/src/components/Map/useRealtimeStream.js

99 lines
2.3 KiB
JavaScript

import { useEffect, useRef, useState, useCallback } from "react";
import { pollNewPosts } from "../../api/client";
/**
* Hook for real-time post streaming using polling (socket.io compatible)
*
* Usage:
* const { newPosts, clearNewPosts } = useRealtimeStream({
* enabled: true,
* lat: 40.7128,
* lon: -74.0060,
* radius_km: 500,
* category: "NEWS",
* pollInterval: 3000 // poll every 3 seconds
* });
*/
export function useRealtimeStream({
enabled = true,
lat,
lon,
radius_km = 500,
category = null,
pollInterval = 3000, // 3 seconds default
onNewPost = null, // callback when new posts arrive
}) {
const [newPosts, setNewPosts] = useState([]);
const [isStreaming, setIsStreaming] = useState(false);
const maxSeenIdRef = useRef(0);
const intervalRef = useRef(null);
const poll = useCallback(async () => {
if (!lat || !lon) return;
try {
const result = await pollNewPosts({
since_id: maxSeenIdRef.current,
lat,
lon,
radius_km,
category,
limit: 20,
});
if (result.posts && result.posts.length > 0) {
setNewPosts((prev) => [...prev, ...result.posts]);
// Update max seen ID
if (result.max_id > maxSeenIdRef.current) {
maxSeenIdRef.current = result.max_id;
}
// Call callback if provided
if (onNewPost) {
result.posts.forEach(onNewPost);
}
}
} catch (err) {
console.warn("Realtime stream poll error:", err);
}
}, [lat, lon, radius_km, category, onNewPost]);
useEffect(() => {
if (!enabled || !lat || !lon) {
setIsStreaming(false);
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return;
}
setIsStreaming(true);
// Start polling
intervalRef.current = setInterval(poll, pollInterval);
// Initial poll
poll();
return () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
setIsStreaming(false);
};
}, [enabled, lat, lon, radius_km, category, pollInterval, poll]);
const clearNewPosts = useCallback(() => {
setNewPosts([]);
}, []);
return {
newPosts,
clearNewPosts,
isStreaming,
};
}