A resilient app reacts to the network: show an "offline" banner when the connection drops, queue writes until it's back, or drop to lower-res images on a slow 2g link. The browser exposes this through two APIs — navigator.onLine plus the online/offline events for connectivity, and the Network Information API (navigator.connection) for quality (effectiveType, downlink, rtt, saveData). useNetworkState reads both and re-renders when either changes.
Implement useNetworkState(). Return { online, effectiveType, downlink, rtt, saveData, type }, seeded on mount, updated on the online/offline window events and the connection's change event, and cleaned up on unmount. Degrade gracefully where navigator.connection isn't supported.
function useNetworkState() {
// returns { online, effectiveType, downlink, rtt, saveData, type }
}
const { online, effectiveType } = useNetworkState();
if (!online) return <OfflineBanner />;
const lowData = effectiveType === '2g' || effectiveType === 'slow-2g';
const { saveData } = useNetworkState();
<Image src={saveData ? lowRes : highRes} />
navigator.onLine (+ online/offline events) for connectivity; navigator.connection for quality.online, offline, and (if present) the connection's change event, re-reading all fields in one handler.undefined when navigator.connection is missing, but still report online.You'll build one readState() that snapshots navigator.onLine plus the connection fields, seed state from it, and re-run it from a single handler wired to the online, offline, and connection change events.
Network state comes from two places that change independently: connectivity (navigator.onLine, which flips on the online/offline events) and quality (navigator.connection.effectiveType/downlink/…, which updates on that object's change event). The clean way to model this is a single "read everything I care about right now" function. Seed state with it, then call it again whenever any of the relevant events fire. The wrinkle is that the Network Information API isn't universal — so you must read online unconditionally but treat the connection fields as optional.
One snapshot function, three triggers. readState() reads navigator.onLine and, if navigator.connection exists, its quality fields — returning a plain object. On mount you seed state with readState(). Then a single update = () => setState(readState()) handler is attached to the window's online and offline events and to the connection object's change event (when there is one). Any of them firing recomputes the whole snapshot, so the returned state is always a consistent, current picture. Cleanup removes all three subscriptions.
The naive version tracks only online/offline and forgets the connection change event:
function useNetworkStateNaive() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const on = () => setOnline(true);
const off = () => setOnline(false);
window.addEventListener('online', on);
window.addEventListener('offline', off);
return () => {
window.removeEventListener('online', on);
window.removeEventListener('offline', off);
};
}, []);
return { online };
}
It works for connectivity but stops there. It never reads effectiveType/downlink, so an app can't adapt to a link degrading (Wi-Fi → 3g) while still "online". Using two handlers (on/off) instead of one recompute also means adding a third event later duplicates logic. A single readState() + one update handler scales to every field and every source.
const { useState, useEffect } = require('react');
function getConnection() {
if (typeof navigator === 'undefined') return undefined;
return navigator.connection || navigator.mozConnection || navigator.webkitConnection;
}
function readState() {
const online = typeof navigator !== 'undefined' ? navigator.onLine : true;
const conn = getConnection();
return {
online,
effectiveType: conn?.effectiveType,
downlink: conn?.downlink,
rtt: conn?.rtt,
saveData: conn?.saveData,
type: conn?.type,
};
}
function useNetworkState() {
const [state, setState] = useState(readState);
useEffect(() => {
const update = () => setState(readState());
window.addEventListener('online', update);
window.addEventListener('offline', update);
const conn = getConnection();
if (conn) conn.addEventListener('change', update);
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
if (conn) conn.removeEventListener('change', update);
};
}, []);
return state;
}
module.exports = { useNetworkState };
readState is the single source of truth: it reads online unconditionally and the connection fields through optional chaining, so a missing navigator.connection simply yields undefined fields instead of throwing. useState(readState) seeds correctly on mount (lazy initializer). The effect wires one update handler to all three events — online, offline, and, when a connection object exists, its change — and the cleanup removes exactly those. Because every trigger runs the same full snapshot, there's no chance of the fields drifting out of sync with each other. getConnection also checks the vendor-prefixed names for older engines.
Mount on a 4g connection, then the device drops to 2g, then goes offline:
readState() returns { online: true, effectiveType: '4g', downlink: 10, … }. State seeded; update attached to online, offline, and connection.change.connection's change event → update() → readState() re-reads effectiveType: '2g', downlink: 0.25. State updates; a data-saving UI can switch to low-res.offline → update() → readState() reads navigator.onLine === false. online becomes false; the offline banner shows.change event.navigator.connection exists — it's not universal; read it through optional chaining and keep online working regardless.readState() + one update keeps every field consistent and easy to extend.removeEventListener('change', …) on the connection object, not just the window ones.useOnlineStatus — the connectivity-only slice ({ online }) is common enough to expose as its own tiny hook.effectiveType/saveData is real adaptive-loading, shipped by large sites.navigator.onLine only means "has a network interface", not "the internet works"; pairing it with a periodic tiny fetch catches captive portals and dead uplinks.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.
A resilient app reacts to the network: show an "offline" banner when the connection drops, queue writes until it's back, or drop to lower-res images on a slow 2g link. The browser exposes this through two APIs — navigator.onLine plus the online/offline events for connectivity, and the Network Information API (navigator.connection) for quality (effectiveType, downlink, rtt, saveData). useNetworkState reads both and re-renders when either changes.
Implement useNetworkState(). Return { online, effectiveType, downlink, rtt, saveData, type }, seeded on mount, updated on the online/offline window events and the connection's change event, and cleaned up on unmount. Degrade gracefully where navigator.connection isn't supported.
function useNetworkState() {
// returns { online, effectiveType, downlink, rtt, saveData, type }
}
const { online, effectiveType } = useNetworkState();
if (!online) return <OfflineBanner />;
const lowData = effectiveType === '2g' || effectiveType === 'slow-2g';
const { saveData } = useNetworkState();
<Image src={saveData ? lowRes : highRes} />
navigator.onLine (+ online/offline events) for connectivity; navigator.connection for quality.online, offline, and (if present) the connection's change event, re-reading all fields in one handler.undefined when navigator.connection is missing, but still report online.You'll build one readState() that snapshots navigator.onLine plus the connection fields, seed state from it, and re-run it from a single handler wired to the online, offline, and connection change events.
Network state comes from two places that change independently: connectivity (navigator.onLine, which flips on the online/offline events) and quality (navigator.connection.effectiveType/downlink/…, which updates on that object's change event). The clean way to model this is a single "read everything I care about right now" function. Seed state with it, then call it again whenever any of the relevant events fire. The wrinkle is that the Network Information API isn't universal — so you must read online unconditionally but treat the connection fields as optional.
One snapshot function, three triggers. readState() reads navigator.onLine and, if navigator.connection exists, its quality fields — returning a plain object. On mount you seed state with readState(). Then a single update = () => setState(readState()) handler is attached to the window's online and offline events and to the connection object's change event (when there is one). Any of them firing recomputes the whole snapshot, so the returned state is always a consistent, current picture. Cleanup removes all three subscriptions.
The naive version tracks only online/offline and forgets the connection change event:
function useNetworkStateNaive() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
const on = () => setOnline(true);
const off = () => setOnline(false);
window.addEventListener('online', on);
window.addEventListener('offline', off);
return () => {
window.removeEventListener('online', on);
window.removeEventListener('offline', off);
};
}, []);
return { online };
}
It works for connectivity but stops there. It never reads effectiveType/downlink, so an app can't adapt to a link degrading (Wi-Fi → 3g) while still "online". Using two handlers (on/off) instead of one recompute also means adding a third event later duplicates logic. A single readState() + one update handler scales to every field and every source.
const { useState, useEffect } = require('react');
function getConnection() {
if (typeof navigator === 'undefined') return undefined;
return navigator.connection || navigator.mozConnection || navigator.webkitConnection;
}
function readState() {
const online = typeof navigator !== 'undefined' ? navigator.onLine : true;
const conn = getConnection();
return {
online,
effectiveType: conn?.effectiveType,
downlink: conn?.downlink,
rtt: conn?.rtt,
saveData: conn?.saveData,
type: conn?.type,
};
}
function useNetworkState() {
const [state, setState] = useState(readState);
useEffect(() => {
const update = () => setState(readState());
window.addEventListener('online', update);
window.addEventListener('offline', update);
const conn = getConnection();
if (conn) conn.addEventListener('change', update);
return () => {
window.removeEventListener('online', update);
window.removeEventListener('offline', update);
if (conn) conn.removeEventListener('change', update);
};
}, []);
return state;
}
module.exports = { useNetworkState };
readState is the single source of truth: it reads online unconditionally and the connection fields through optional chaining, so a missing navigator.connection simply yields undefined fields instead of throwing. useState(readState) seeds correctly on mount (lazy initializer). The effect wires one update handler to all three events — online, offline, and, when a connection object exists, its change — and the cleanup removes exactly those. Because every trigger runs the same full snapshot, there's no chance of the fields drifting out of sync with each other. getConnection also checks the vendor-prefixed names for older engines.
Mount on a 4g connection, then the device drops to 2g, then goes offline:
readState() returns { online: true, effectiveType: '4g', downlink: 10, … }. State seeded; update attached to online, offline, and connection.change.connection's change event → update() → readState() re-reads effectiveType: '2g', downlink: 0.25. State updates; a data-saving UI can switch to low-res.offline → update() → readState() reads navigator.onLine === false. online becomes false; the offline banner shows.change event.navigator.connection exists — it's not universal; read it through optional chaining and keep online working regardless.readState() + one update keeps every field consistent and easy to extend.removeEventListener('change', …) on the connection object, not just the window ones.useOnlineStatus — the connectivity-only slice ({ online }) is common enough to expose as its own tiny hook.effectiveType/saveData is real adaptive-loading, shipped by large sites.navigator.onLine only means "has a network interface", not "the internet works"; pairing it with a periodic tiny fetch catches captive portals and dead uplinks.Keep practising the same patterns with a nearby challenge.
No submissions yet
Share your approach and start the discussion.