@tiendanube/live-state
v1.3.0
Published
Generic React library for rendering live state notifications
Maintainers
Readme
@tiendanube/live-state
React library for rendering dynamic, context-aware notifications based on backend-computed state
A generic React library for rendering live state notifications (banners, alerts) driven entirely by backend-computed state. Built for the Tiendanube/Nuvemshop ecosystem using the Nimbus Design System.
Features
- Backend-controlled — all content and logic comes from the API, zero hardcoding
- Type-safe — full TypeScript support with strict types
- Analytics built-in — automatic Amplitude + Clarity tracking for view, click, and close events
- Graceful degradation — fails silently, never breaks the host application
- Nimbus Design System — uses
@nimbus-ds/components
Installation
yarn add @tiendanube/live-stateInstall peer dependencies if not already present:
yarn add react react-dom @nimbus-ds/components @nimbus-ds/iconsQuick start
1. Add the provider
import { LiveStateProvider } from '@tiendanube/live-state';
async function fetchLiveState() {
try {
const res = await fetch('/api/lending/live-state', {
headers: { Authorization: `Bearer ${token}` },
});
return res.status === 204 ? null : res.json();
} catch {
return null;
}
}
function App() {
return (
<LiveStateProvider
fetcher={fetchLiveState}
analytics={{
amplitudeKey: process.env.REACT_APP_AMPLITUDE_KEY,
clarityProjectId: process.env.REACT_APP_CLARITY_PROJECT_ID,
}}
>
<YourApp />
</LiveStateProvider>
);
}2. Render notifications
import { useLiveState, LiveStateRenderer } from '@tiendanube/live-state';
function LendingNotifications() {
const { liveState, isLoading } = useLiveState();
return (
<LiveStateRenderer
data={liveState}
loading={isLoading}
trackingConfig={{ prefix: 'lending_', page: 'dashboard' }}
/>
);
}That's it. The library handles rendering, tracking, and CTA behavior automatically.
Caching behaviour (SWR)
The lib uses SWR internally for data fetching, caching, and deduplication. Key implications:
- Single request per session — all
useLiveState()calls inside the sameLiveStateProvidershare one cached result. The backend is called once, regardless of how many pages or components use the hook. - Cache key is fixed — the cache key is the string
'live-state', not the fetcher URL. This means changing thepageprop onLiveStateRendererdoes not trigger a new request — the page is a frontend-only concern for tracking event names. - Fetcher reference stability — SWR does not re-fetch when the fetcher function reference changes, because the key is fixed. Even so, it is good practice to define the fetcher outside the component (or use
useCallback/useMemo) to avoid unnecessary re-renders. - Revalidation on reconnect — the lib revalidates automatically when the browser reconnects to the network.
- No revalidation on focus — focus-based revalidation is disabled to avoid extra requests when the user switches tabs.
- Manual refresh — call
refresh()fromuseLiveState()to force a re-fetch at any time.
// ✅ Define fetcher outside the component — stable reference, no re-renders
const fetcher: LiveStateFetcher = async () => { ... };
function App() {
return <LiveStateProvider fetcher={fetcher}>...</LiveStateProvider>;
}
// ❌ Avoid defining fetcher inline — new reference on every render
function App() {
return (
<LiveStateProvider fetcher={async () => { ... }}>...</LiveStateProvider>
);
}Testing utilities
Import from @tiendanube/live-state/testing in your test files:
import {
MockLiveStateProvider,
createMockLiveState,
mockUseLiveState,
} from '@tiendanube/live-state/testing';
// Render with controlled live state data
render(
<MockLiveStateProvider liveState={createMockLiveState({ type: 'alert' })}>
<MyComponent />
</MockLiveStateProvider>
);
// Mock the hook directly in unit tests
vi.mock('@tiendanube/live-state', async (importOriginal) => ({
...(await importOriginal()),
useLiveState: () => mockUseLiveState({ liveState: createMockLiveState() }),
}));MockLiveStateProvider automatically sets disabled={true}, suppressing all analytics SDK calls in tests.
Disabling analytics (dev / test / staging)
Pass disabled={true} to LiveStateProvider to prevent the Amplitude and Clarity SDKs from being initialised. The prop value should come from your app's own environment variable — the library does not define or read any env var itself.
// Vite
<LiveStateProvider
fetcher={fetchLiveState}
disabled={import.meta.env.VITE_APP_ENV !== 'production'}
>
...
</LiveStateProvider>
// Create React App / Next.js
<LiveStateProvider
fetcher={fetchLiveState}
disabled={process.env.NODE_ENV !== 'production'}
>
...
</LiveStateProvider>When disabled is true, onEvent callbacks still fire normally so you can observe tracking without hitting the real SDKs.
Fetcher timeouts
The library does not impose a timeout on the fetcher — if the endpoint hangs, isLoading will remain true indefinitely. Because live state notifications are non-critical UI, you should configure a timeout directly in your fetcher so that a slow backend never blocks the page.
// Using axios
const fetcher: LiveStateFetcher = async () => {
const { data } = await axios.get('/api/live-state', { timeout: 5000 });
return data ?? null;
};
// Using native fetch + AbortSignal
const fetcher: LiveStateFetcher = async () => {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('/api/live-state', { signal: controller.signal });
return res.ok ? await res.json() : null;
} catch {
return null; // timeout or network error — fail silently
} finally {
clearTimeout(id);
}
};Documentation
- Implementation Guide — full integration guide with usage patterns, API reference, and troubleshooting
- Architecture — internal design decisions
- Versioning & Release — how to publish new versions
- CircleCI Setup — CI/CD configuration
- Contributing — developer contribution guide
Contributing
This is an internal Tiendanube/Nuvemshop library. For questions or issues, contact the team.
License
MIT © Tiendanube/Nuvemshop
