Adding Chromecast MAC & State to Inventory, Detail, and Global Search (React + TypeScript)
TL;DR: I extended the device model to expose chromecastMac, chromecastState, and stateModified, then updated the inventory table, device detail sheet, and global search components to render those fields. The change required a few type augmentations, column definitions, and UI tweaks, but it also exposed a hidden data‑flow bug that I fixed by normalizing the store shape.
The Problem
Our TV‑monitoring UI (tvview) displays a list of devices in an inventory table, a detail sheet per device, and a global search overlay. Chromecast devices were missing two critical pieces of information:
- MAC address – needed for network diagnostics.
- Current Chromecast state – useful for troubleshooting “offline” reports.
The UI showed a blank column for “State” because the TvDeviceRow type didn’t include the fields, and the backend API was already returning chromecastMac and chromecastState. The symptom was a TypeScript error in DeviceDetailSheet.tsx:
Property 'chromecastMac' does not exist on type 'DeviceDetail'.
Additionally, the global search results omitted any reference to Chromecast devices, making it impossible to locate a specific unit by MAC.
What I Tried First
My first instinct was to patch the UI components with ad‑hoc any casts:
const mac = (device as any).chromecastMac ?? '—';
That silenced the TypeScript errors, but it introduced two problems:
- Loss of type safety – future contributors could accidentally pass the wrong shape.
-
Runtime undefined – the store still returned
nullfor those fields because the hookuseDevicesStoredidn’t expose them.
I also tried adding a new Redux slice just for Chromecast metadata, but that duplicated data already present in the main devices slice and complicated the selector logic.
The Implementation
1. Extend the Device Types
In src/hooks/use-devices-store.ts I added the missing properties to the TvDeviceRow interface:
@@ -19,6 +19,9 @@ export interface TvDeviceRow {
isChromecast: boolean;
occupied: boolean | null;
chromecastState: string | null;
+ chromecastMac: string | null;
+ stateModified: string | null;
}
-
chromecastMac– the MAC address string (ornullif unavailable). -
stateModified– a timestamp string indicating the last state change; useful for UI diffing.
No other files needed to be updated because the hook already spreads the API payload into the store; the new fields are now part of the returned objects.
2. Update the Detail Sheet
src/features/devices/DeviceDetailSheet.tsx required three new UI rows. I added them after the existing lineup row:
@@ -47,6 +47,9 @@ interface DeviceDetail {
cable: string | null;
lineup: string | null;
isChromecast: boolean;
+ chromecastState: string | null;
+ chromecastMac: string | null;
+ stateModified: string | null;
}
And the JSX:
{device.isChromecast && (
<>
<DetailRow label="Chromecast MAC" value={device.chromecastMac ?? '—'} />
<DetailRow label="Chromecast State" value={device.chromecastState ?? '—'} />
<DetailRow label="State Modified" value={device.stateModified ?? '—'} />
</>
)}
DetailRow is a reusable component that renders a label/value pair with consistent styling.
3. Add Columns to the Inventory Table
In src/features/inventory/InventoryTable.tsx I introduced a new column definition for the MAC address and adjusted the existing “State” column to use the newly typed field:
@@ -66,6 +66,18 @@ const columns: ColumnDef<TvDeviceRow>[] = [
),
},
+{
+ accessorKey: "chromecastMac",
+ header: "MAC Chromecast",
+ cell: (info) => {
+ const mac = info.getValue() as string | null;
+ return <span className="font-mono">{mac ?? '—'}</span>;
+ },
+},
+{
+ accessorKey: "chromecastState",
+ header: "Chromecast State",
+ cell: (info) => <span>{info.getValue() ?? '—'}</span>,
+},
The table now displays the MAC address in a monospaced font, making it easy to copy‑paste.
4. Surface the Data in Global Search
src/features/search/GlobalSearch.tsx needed a small UI tweak to include the MAC in the result list. I added a conditional block inside the map over search results:
@@ -71,10 +71,17 @@ export function GlobalSearch() {
<span className="font-technical truncate text-sm font-medium">{d.room}</span>
<span className="text-muted-for
- {/* existing fields */}
+ {/* existing fields */}
+ {d.isChromecast && d.chromecastMac && (
+ <span className="text-xs text-muted ml-2">
+ MAC: <code>{d.chromecastMac}</code>
+ </span>
+ )}
+ {d.isChromecast && d.chromecastState && (
+ <span className="text-xs text-muted ml-2">
+ State: {d.chromecastState}
+ </span>
+ )}
Now a user typing “Living Room” will see the matching Chromecast’s MAC and state directly in the dropdown.
5. Verify the Data Flow
Running the dev server (npm run dev) revealed a subtle bug: the chromecastMac field was undefined for a subset of devices because the API returned it under the key macAddress. I fixed this by normalizing the payload in the store’s async thunk:
// src/hooks/use-devices-store.ts
const normalized = rawDevices.map(dev => ({
...dev,
chromecastMac: dev.macAddress ?? null,
chromecastState: dev.state ?? null,
stateModified: dev.stateModified ?? null,
}));
setDevices(normalized);
With that mapping in place, the UI now consistently shows the MAC and state across all components.
Key Takeaway
Never rely on the shape of an external API without normalizing it first. Adding explicit type extensions is only half the battle; you must also ensure the data you receive matches those extensions, otherwise you’ll silently render undefined values or, worse, introduce runtime bugs.
What's Next
The next step is to enable inline editing of the Chromecast MAC directly from the inventory table. I’ll add a modal that validates MAC format (/^([0-9A-F]{2}:){5}[0-9A-F]{2}$/i) and persists the change via the existing /api/devices/:id endpoint. This will complete the “read‑write” loop for Chromecast metadata.
Roberto Luna Osorio – Full Stack Developer & Project Lead
Playa del Carmen, México
vibecoding #buildinpublic #react #typescript #frontend #chromecast
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/tvview · 2026-10-31
#playadev #buildinpublic
Top comments (0)