Weather Station / ECOWITT / DNT
1"use client";
2
3import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
4import { API_ENDPOINTS } from '@/constants';
5
6type RTData = any;
7
8/**
9 * Defines the shape of the RealtimeContext.
10 */
11interface RealtimeContextType {
12 /** The most recent real-time data object. */
13 data: RTData | null;
14 /** Any error message from the last fetch attempt. */
15 error: string | null;
16 /** True if data is currently being fetched. */
17 loading: boolean;
18 /** The timestamp of the last successful data update. */
19 lastUpdated: Date | null;
20 /** A function to manually trigger a data refetch. */
21 refetch: () => void;
22}
23
24const RealtimeContext = createContext<RealtimeContextType | undefined>(undefined);
25
26/**
27 * A React context provider that fetches and manages real-time weather data.
28 * It periodically refetches the data and makes it available to all child components.
29 * @param props - The component props.
30 * @param props.children - The child components that will consume the context.
31 * @returns A RealtimeContext.Provider component.
32 */
33export function RealtimeProvider({ children }: { children: React.ReactNode }) {
34 const [data, setData] = useState<RTData | null>(null);
35 const [error, setError] = useState<string | null>(null);
36 const [loading, setLoading] = useState<boolean>(false);
37 const [lastUpdated, setLastUpdated] = useState<Date | null>(null);
38
39 const fetchData = useCallback(async () => {
40 console.log('[RealtimeContext] fetchData called');
41 try {
42 setLoading(true);
43 setError(null);
44 const res = await fetch(API_ENDPOINTS.RT_LAST, { cache: "no-store" });
45 if (!res.ok) throw new Error(`HTTP ${res.status}`);
46 const rec = await res.json();
47 if (!rec || rec.ok === false) {
48 const msg = rec?.error || 'No data available';
49 setError(msg);
50 return;
51 }
52 setData(rec.data ?? null);
53 setLastUpdated(rec.updatedAt ? new Date(rec.updatedAt) : new Date());
54 } catch (e: any) {
55 setError(e?.message || String(e));
56 } finally {
57 setLoading(false);
58 }
59 }, []);
60
61 useEffect(() => {
62 console.log('[RealtimeContext] useEffect running, setting up interval');
63 fetchData();
64 const refreshMs = Number(process.env.NEXT_PUBLIC_RT_REFRESH_MS || 300000); // default 5 min
65 const id = setInterval(() => {
66 console.log('[RealtimeContext] Interval tick');
67 fetchData();
68 }, isFinite(refreshMs) && refreshMs > 0 ? refreshMs : 300000);
69 return () => {
70 console.log('[RealtimeContext] Cleaning up interval');
71 clearInterval(id);
72 };
73 }, []); // Remove fetchData from dependencies to prevent infinite loop
74
75 return (
76 <RealtimeContext.Provider value={{
77 data,
78 error,
79 loading,
80 lastUpdated,
81 refetch: fetchData
82 }}>
83 {children}
84 </RealtimeContext.Provider>
85 );
86}
87
88/**
89 * A custom hook to access the real-time weather data context.
90 * Throws an error if used outside of a `RealtimeProvider`.
91 * @returns The real-time context, including data, loading state, errors, and a refetch function.
92 */
93export function useRealtime() {
94 const context = useContext(RealtimeContext);
95 if (context === undefined) {
96 throw new Error('useRealtime must be used within a RealtimeProvider');
97 }
98 return context;
99}