Integrating Favorites in Frontend
This guide shows how to integrate the favorites API into your frontend application using JavaScript.
LocalStorage Keys
Section titled “LocalStorage Keys”Store favorites data using these standardized keys:
const FAVORITES_KEY = 'idx_favorites';const AUTH_TOKEN_KEY = 'idx_auth_token';const AUTH_EMAIL_KEY = 'idx_auth_email';Basic Helpers
Section titled “Basic Helpers”Get Favorites from Storage
Section titled “Get Favorites from Storage”function getFavorites() { const stored = localStorage.getItem(FAVORITES_KEY); return stored ? JSON.parse(stored) : [];}Check Authentication Status
Section titled “Check Authentication Status”function isAuthenticated() { return !!localStorage.getItem(AUTH_TOKEN_KEY);}Save Favorites Locally
Section titled “Save Favorites Locally”function saveFavorites(favorites) { localStorage.setItem(FAVORITES_KEY, JSON.stringify(favorites));
// Dispatch event for UI updates window.dispatchEvent(new CustomEvent('favoritesChanged', { detail: favorites }));}Sync to Server
Section titled “Sync to Server”Fire-and-Forget Sync
Section titled “Fire-and-Forget Sync”After user authenticates, sync favorites to the server without blocking UI:
async function syncToServer(favorites) { const token = localStorage.getItem(AUTH_TOKEN_KEY); if (!token) return; // Not authenticated yet
try { const response = await fetch('/api/v1/favorites/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ favorites }), });
if (!response.ok) { console.error('Favorites sync failed:', response.status); return; }
const data = await response.json();
// Server may return additional favorites from other devices saveFavorites(data.favorites); } catch (err) { console.error('Favorites sync error:', err); }}Toggle Favorite
Section titled “Toggle Favorite”Add or Remove Favorite
Section titled “Add or Remove Favorite”async function toggleFavorite(listingId) { let favorites = getFavorites(); const index = favorites.indexOf(listingId);
if (index > -1) { // Remove from favorites favorites.splice(index, 1); saveFavorites(favorites);
// Sync deletion to server if authenticated const token = localStorage.getItem(AUTH_TOKEN_KEY); if (token) { await fetch(`/api/v1/favorites/${listingId}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}`, }, }); } } else { // Add to favorites favorites.push(listingId); saveFavorites(favorites);
// Sync addition to server if authenticated syncToServer(favorites); }}Magic Link Flow
Section titled “Magic Link Flow”Request Magic Link
Section titled “Request Magic Link”async function requestMagicLink(email) { const response = await fetch('/api/v1/favorites/magic-link', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Session-ID': getSessionId(), // Optional session tracking }, body: JSON.stringify({ email }), });
if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to send magic link'); }
return response.json();}Verify Magic Link Token
Section titled “Verify Magic Link Token”Called when user clicks email link:
async function verifyMagicLink(token) { const response = await fetch(`/api/v1/favorites/verify/${token}`);
if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Magic link verification failed'); }
const data = await response.json();
// Store auth token localStorage.setItem(AUTH_TOKEN_KEY, data.auth_token); localStorage.setItem(AUTH_EMAIL_KEY, data.email);
// Merge server favorites with local favorites const localFavorites = getFavorites(); const mergedFavorites = [...new Set([...localFavorites, ...data.favorites])]; saveFavorites(mergedFavorites);
// Sync merged set back to server await syncToServer(mergedFavorites);
// Dispatch auth change event window.dispatchEvent(new CustomEvent('authChanged', { detail: { authenticated: true, email: data.email } }));
return data;}Custom Events
Section titled “Custom Events”Listen for favorites changes across your application:
// Listen for favorites changeswindow.addEventListener('favoritesChanged', (event) => { const favorites = event.detail; // Array of listing IDs updateUI(favorites);});
// Listen for auth changeswindow.addEventListener('authChanged', (event) => { const { authenticated, email } = event.detail; if (authenticated) { showWelcomeMessage(email); }});React Hook Example
Section titled “React Hook Example”Create a reusable hook for favorites management:
import { useState, useEffect } from 'react';
function useFavorites() { const [favorites, setFavorites] = useState<string[]>([]); const [isAuthenticated, setIsAuthenticated] = useState(false);
useEffect(() => { // Load initial state setFavorites(getFavorites()); setIsAuthenticated(!!localStorage.getItem(AUTH_TOKEN_KEY));
// Listen for changes const handleFavoritesChange = (e: CustomEvent) => { setFavorites(e.detail); };
const handleAuthChange = (e: CustomEvent) => { setIsAuthenticated(e.detail.authenticated); };
window.addEventListener('favoritesChanged', handleFavoritesChange); window.addEventListener('authChanged', handleAuthChange);
return () => { window.removeEventListener('favoritesChanged', handleFavoritesChange); window.removeEventListener('authChanged', handleAuthChange); }; }, []);
const toggleFavorite = async (listingId: string) => { await toggleFavorite(listingId); // Uses global function };
const isFavorite = (listingId: string) => { return favorites.includes(listingId); };
return { favorites, isAuthenticated, toggleFavorite, isFavorite, };}
export default useFavorites;Using the Hook
Section titled “Using the Hook”function PropertyCard({ listingId }) { const { isFavorite, toggleFavorite } = useFavorites();
return ( <div> <button onClick={() => toggleFavorite(listingId)}> {isFavorite(listingId) ? '★' : '☆'} </button> </div> );}Error Handling
Section titled “Error Handling”Handle Expired Tokens
Section titled “Handle Expired Tokens”async function syncToServer(favorites) { const token = localStorage.getItem(AUTH_TOKEN_KEY); if (!token) return;
try { const response = await fetch('/api/v1/favorites/sync', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ favorites }), });
if (response.status === 401) { // Token expired - clear auth and prompt re-authentication localStorage.removeItem(AUTH_TOKEN_KEY); localStorage.removeItem(AUTH_EMAIL_KEY);
window.dispatchEvent(new CustomEvent('authChanged', { detail: { authenticated: false } }));
// Optionally prompt user to re-authenticate showReauthenticationPrompt(); return; }
if (!response.ok) { throw new Error(`Sync failed: ${response.status}`); }
const data = await response.json(); saveFavorites(data.favorites); } catch (err) { console.error('Favorites sync error:', err); }}Session Tracking
Section titled “Session Tracking”For analytics and session linking:
function getSessionId() { let sessionId = sessionStorage.getItem('session_id');
if (!sessionId) { sessionId = crypto.randomUUID(); sessionStorage.setItem('session_id', sessionId); }
return sessionId;}Pass session ID when requesting magic link to link anonymous browsing to authenticated account.
Related
Section titled “Related”- Favorites API Reference — Endpoint specifications
- Favorites Architecture — How it works under the hood