# Live Dashboard

> Consume a live metrics store with useSyncExternalStore: start and stop polling with the component lifecycle, pause while the tab is hidden, and render loading, live, error, and paused states.

- Difficulty: beginner
- Tags: useSyncExternalStore, useEffect, polling, timers, visibility
- Estimated time: 30 minutes
- Tests: 10
- Requires subscription: yes

## Overview

Consume a provided live metrics store with `useSyncExternalStore` and orchestrate its polling lifecycle, tab visibility, and UI states.


## Requirements

- The dashboard reads its data from the provided store with `useSyncExternalStore` (`dashboardStore.subscribe` and `dashboardStore.getSnapshot`); `store.ts` is hidden and must be used as-is.
- While the first request is in flight, the status (`data-testid="status"`) reads `Updating…`, the skeleton (`data-testid="dashboard-skeleton"`) is visible, and no metric cards are rendered.
- After the first successful response, the status reads `Live` and the dashboard renders four metric cards: `data-testid="metric-active-users"`, `metric-revenue`, `metric-requests`, and `metric-error-rate`, each showing its value in `data-testid="metric-<id>-value"`.
- The last successful update is displayed in `data-testid="last-updated"` with the format `Updated at HH:MM:SS`.
- Polling starts when the dashboard mounts on a visible document, so the cards update without user interaction.
- While a later poll (automatic or manual) is in flight on a visible document, the status reads `Updating…` and returns to `Live` when it succeeds, keeping the current cards on screen instead of the skeleton.
- Polling stops when the dashboard unmounts.
- The refresh button (`data-testid="refresh"`) fetches immediately instead of waiting for the next interval, keeping the previous cards visible while the request is in flight.
- The refresh button is disabled while the status is `Updating…`.
- When a request fails, the status reads `Error` and `data-testid="error-message"` contains the failure message.
- After a failure, the last successful metric values stay on screen.
- Polling retries on the next interval after a failure.
- When the document becomes hidden, the status reads `Paused` and no further polling requests are made.
- When the document becomes visible again, the dashboard refreshes immediately and resumes polling.


## Notes

- The provided `store.ts` is hidden on purpose. It exports the `dashboardStore` singleton with `subscribe`, `getSnapshot`, `start`, `refresh`, `pause`, and `resume`; `Components.tsx`, `data.ts`, and `types.ts` are read-only.
- `getSnapshot` returns the current snapshot. Pass it directly to `useSyncExternalStore`; do not copy the data into `useState` or wrap it in a function that builds a new object.
- `start` returns the cleanup function that stops polling. The effect that starts the dashboard must return it.
- React StrictMode mounts effects twice in development. Keep that in mind when an effect starts a timer.
- Pausing is not only about stopping the interval; think about what to do with a request that is already in flight.
- The Page Visibility API exposes whether the tab is hidden, and the tests simulate that state.
- APIs: [`useSyncExternalStore`](https://react.dev/reference/react/useSyncExternalStore), [Page Visibility API](https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API).


## Tests

01. shows the skeleton and the updating status on mount
02. renders the four metrics after the first fetch
03. shows the time of the last successful update
04. polls every five seconds and updates the metrics
05. refreshes immediately when the refresh button is clicked
06. shows an error and keeps the last successful data
07. retries on the next interval after an error
08. pauses polling while the tab is hidden
09. refreshes and resumes polling when the tab becomes visible
10. stops polling when the dashboard unmounts


