Rules Hub
Coding Rules Library
Rule priority, scope & exceptions
Use this to align rules with the senior-level structure (P0/P1/P2, scope, exceptions/tradeoffs).
Pair a streaming channel with declarative polling + reconnect fallback
On SSE/WebSocket error, flip a failed flag that declaratively drives both a polling interval and a reconnect-retry interval; reopening the stream clears it.
Bad example
| 1 | // Stream with no fallback — if it errors once, data silently goes stale forever. |
| 2 | useEffect(() => { |
| 3 | const es = new EventSource(url); |
| 4 | es.onmessage = e => addArticle(JSON.parse(e.data)); |
| 5 | es.onerror = () => es.close(); // closed, never retried, no polling backup |
| 6 | return () => es.close(); |
| 7 | }, []); |
Explanation (EN)
A streaming connection is the unreliable part of the system — proxies time out, networks drop, the server restarts. Closing on error with no fallback means the UI freezes on the last payload with no recovery and no fresh data. Hand-rolled imperative reconnection (nested setTimeouts inside onerror) tends to leak timers and double-connect.
Objašnjenje (HR)
Streaming veza je nepouzdan dio sustava — proxyji istječu, mreža pada, server se restarta. Zatvaranje na grešci bez fallbacka znači da se UI zaledi na zadnjem podatku bez oporavka i bez svježih podataka. Ručno imperativno ponovno spajanje (ugniježđeni setTimeouti u onerror) obično curi tajmere i otvara dvostruke veze.
Good example
| 1 | const [streamFailed, setStreamFailed] = useState(false); |
| 2 | const esRef = useRef<EventSource | null>(null); |
| 3 |
|
| 4 | const connect = useCallback(() => { |
| 5 | if (typeof window === 'undefined' || esRef.current) return; |
| 6 | const es = new EventSource(url); |
| 7 | esRef.current = es; |
| 8 | es.onopen = () => setStreamFailed(false); // healthy again -> stops fallbacks |
| 9 | es.onmessage = e => addArticle(JSON.parse(e.data)); |
| 10 | es.onerror = () => { es.close(); esRef.current = null; setStreamFailed(true); }; |
| 11 | }, []); |
| 12 |
|
| 13 | useEffect(() => { fetchData(); connect(); return () => esRef.current?.close(); }, []); |
| 14 |
|
| 15 | // One flag declaratively drives both recovery mechanisms: |
| 16 | useInterval(fetchData, streamFailed ? POLLING_INTERVAL : null); // keep data fresh |
| 17 | useInterval(() => { if (!esRef.current) connect(); }, streamFailed ? RETRY_INTERVAL : null); // re-establish stream |
Explanation (EN)
The stream is primary; a single `streamFailed` flag is the source of truth that declaratively activates a polling interval (so data stays fresh) and a reconnect-retry interval (so the stream self-heals). When `onopen` fires it clears the flag, which automatically stops both fallbacks. State, not imperative timer juggling, expresses the recovery policy.
Objašnjenje (HR)
Stream je primaran; jedna `streamFailed` zastavica je izvor istine koji deklarativno aktivira interval pollinga (da podaci ostanu svježi) i interval ponovnog spajanja (da se stream sam oporavi). Kad se okine `onopen`, zastavica se očisti, što automatski zaustavlja oba fallbacka. Stanje, a ne ručno žongliranje tajmerima, izražava politiku oporavka.
Notes (EN)
Works the same for WebSocket. Keep the connection handle in a ref and null it on error so the retry interval can detect 'no live connection' and only then reconnect.
Bilješke (HR)
Jednako radi za WebSocket. Handle veze drži u refu i postavi ga na null pri grešci kako bi retry interval prepoznao 'nema žive veze' i tek tada se ponovno spojio.