Streambert Library Watchlist Data Persistence and Drag‑and‑Drop Reordering: A Complete Technical Guide
Streambert persists watchlist data to the browser's localStorage using a centralized storage utility and supports drag‑and‑drop reordering through React refs that update a savedOrder array, ensuring all changes are instantly written to local storage without requiring API calls.
Streambert is an open-source streaming interface that manages user watchlists entirely client-side. The library implements a robust persistence layer for watchlist data and intuitive drag‑and‑drop reordering capabilities, storing all state locally in the browser. Understanding these mechanisms is essential for developers looking to customize watchlist behavior or integrate Streambert's local-first architecture into their own React applications.
How Streambert Persists Watchlist Data
The Storage Utility in src/utils/storage.js
All watchlist persistence flows through a thin wrapper around the Web Storage API defined in src/utils/storage.js. This utility namespaces every key with streambert_ to avoid collisions with other applications sharing the same origin.
The storage object exposes get and set methods that handle JSON serialization and error suppression. When writing watchlist changes, the set method automatically encodes values and writes them to localStorage (lines 5‑18):
// src/utils/storage.js
const PREFIX = "streambert_";
export const storage = {
get(key) {
try {
const raw = localStorage.getItem(PREFIX + key);
return raw ? JSON.parse(raw) : null;
} catch {
return null;
}
},
set(key, value) {
try {
localStorage.setItem(PREFIX + key, JSON.stringify(value));
} catch {}
}
};
Two primary keys manage the watchlist state:
saved: A serialized object mapping title IDs to boolean values, representing the user's saved titles.savedOrder: An optional array that stores the user‑defined sequence of titles, enabling custom ordering beyond insertion time.
Watchlist State Management in App.jsx
The main application component in src/App.jsx initializes the watchlist state by hydrating from localStorage on mount (lines 55‑56). It uses lazy initialization with useState to read from storage only once during component construction:
// src/App.jsx
const [saved, setSaved] = useState(() => storage.get(STORAGE_KEYS.SAVED) || {});
const [savedOrder, setSavedOrder] = useState(() => storage.get("savedOrder") || null);
When users add or remove titles, the application updates both React state and local storage synchronously. The addToWatchlist function (lines 674‑690) merges the new title into the existing saved object and immediately persists the change:
// Adding a title
const addToWatchlist = (id) => {
const newSaved = { ...saved, [id]: true };
setSaved(newSaved);
storage.set(STORAGE_KEYS.SAVED, newSaved);
showToast("Added to watchlist");
};
// Removing a title
const removeFromWatchlist = (id) => {
const { [id]: _, ...rest } = saved;
setSaved(rest);
storage.set(STORAGE_KEYS.SAVED, rest);
showToast("Removed from watchlist");
};
Every mutation triggers storage.set(), ensuring the watchlist data persistence remains consistent across browser sessions and tab refreshes.
Implementing Drag‑and‑Drop Reordering
Streambert supports two distinct drag‑and‑drop interactions that modify the watchlist ordering: home‑page row arrangement and saved‑item list ordering. Both implementations rely on React refs to track the dragged element and update the order array in‑place before persisting.
Home‑Page Row Arrangement in SettingsPage.jsx
Users can customize which content rows appear on the Home page and reorder them via drag‑and‑drop. This logic resides in src/pages/SettingsPage.jsx (lines 696‑710), where the component maintains dragItem and dragOver refs to track the source and target indices:
// src/pages/SettingsPage.jsx
const dragItem = useRef(null);
const dragOver = useRef(null);
const handleDragStart = (e, idx) => {
dragItem.current = idx;
};
const handleDragEnter = (e, idx) => {
dragOver.current = idx;
};
const handleDrop = () => {
const newOrder = [...homeRowOrder];
const dragged = newOrder.splice(dragItem.current, 1)[0];
newOrder.splice(dragOver.current, 0, dragged);
setHomeRowOrder(newOrder);
storage.set("homeRowOrder", newOrder);
dragItem.current = null;
dragOver.current = null;
};
The UI explicitly indicates this functionality with the instruction: "Choose which rows appear on the Home page and drag to reorder them" (line 739).
Saved‑Item List Ordering in Sidebar.jsx
Within the Sidebar component, users can reorder individual saved titles by dragging thumbnails. The implementation in src/components/Sidebar.jsx (lines 28‑67) uses three tracking variables: dragItem (source index), dragNode (DOM reference), and dragOver (target index):
// src/components/Sidebar.jsx
const dragItem = useRef(null);
const dragNode = useRef(null);
const [dragOver, setDragOver] = useState(null);
const handleDragStart = (e, index) => {
dragItem.current = index;
dragNode.current = e.currentTarget;
dragNode.current.style.opacity = "0.4";
};
const handleDragEnter = (e, index) => {
setDragOver(index);
};
const handleDragEnd = () => {
if (dragNode.current) dragNode.current.style.opacity = "1";
const newOrder = [...savedOrder];
const dragged = newOrder.splice(dragItem.current, 1)[0];
newOrder.splice(dragOver, 0, dragged);
setSavedOrder(newOrder);
storage.set("savedOrder", newOrder);
dragItem.current = null;
dragNode.current = null;
setDragOver(null);
};
Each saved thumbnail renders with the draggable attribute, and the component applies a visual highlight when an item is dragged over a valid drop target (lines 145‑167). When the drag operation completes, the code rebuilds the savedOrder array and writes it to localStorage, ensuring the Streambert watchlist data persistence reflects the new sequence immediately.
Backup and Restore Functionality
Streambert includes a complete backup and restore system in src/pages/SettingsPage.jsx (lines 30‑84) that exports the entire watchlist state to a JSON file. This captures both the saved object and savedOrder array:
// Exporting watchlist data
const handleExport = () => {
const data = {
data: {
saved: storage.get(STORAGE_KEYS.SAVED),
savedOrder: storage.get("savedOrder")
}
};
const blob = new Blob([JSON.stringify(data, null, 2)], {
type: "application/json"
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "streambert-backup.json";
a.click();
URL.revokeObjectURL(url);
};
The import function reads the JSON file, validates the data field, and restores each key via storage.set(), enabling users to migrate their watchlists across devices or browsers while preserving custom drag‑and‑drop ordering.
Summary
- Streambert uses a centralized storage utility in
src/utils/storage.jsto namespace and serialize watchlist data tolocalStorageunder thestreambert_prefix. - The watchlist state consists of two structures: a
savedobject mapping title IDs and an optionalsavedOrderarray for custom sequencing. - Drag‑and‑drop reordering is implemented in
Sidebar.jsx(for saved titles) andSettingsPage.jsx(for home‑page rows) using React refs to track indices andstorage.set()to persist changes. - All persistence operations are synchronous and client‑side, requiring no network requests or external APIs.
- The backup system exports both data structures to JSON, allowing full restoration of watchlist state including custom ordering.
Frequently Asked Questions
How does Streambert store watchlist data without a backend?
Streambert writes all watchlist state to the browser's localStorage using a wrapper utility in src/utils/storage.js. This approach stores data locally on the user's device under the streambert_ namespace, eliminating the need for server-side persistence while ensuring data survives page refreshes.
What is the difference between the saved and savedOrder keys?
The saved key contains an object mapping title IDs to boolean values, indicating which titles are in the watchlist. The savedOrder key contains an array that defines the specific sequence in which those titles should appear. When savedOrder exists, Streambert uses it to render the watchlist; otherwise, it falls back to default ordering.
Can I programmatically reorder the watchlist without using the drag‑and‑drop UI?
Yes. You can manipulate the savedOrder array directly using the storage utility and update the React state accordingly. Retrieve the current order with storage.get("savedOrder"), modify the array to your desired sequence, then persist it with storage.set("savedOrder", newOrder) and call setSavedOrder(newOrder) to update the UI.
Where is the drag‑and‑drop logic implemented in the Streambert codebase?
The library implements two drag‑and‑drop systems: home‑page row reordering lives in src/pages/SettingsPage.jsx (lines 696‑710), while saved‑item reordering within the sidebar is handled in src/components/Sidebar.jsx (lines 28‑67). Both use native HTML5 drag‑and‑drop events coupled with React refs to track and persist ordering changes.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →