Real-time Features
This guide covers implementing real-time features using Convex in your Twigz application.Overview
Twigz is built on Convex, which provides automatic real-time subscriptions and live data updates.Basic Real-time Setup
1. Convex Client Setup
Copy
import { ConvexProvider, ConvexReactClient } from "convex/react";
const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
function App() {
return (
<ConvexProvider client={convex}>
<YourApp />
</ConvexProvider>
);
}
2. Real-time Queries
Copy
import { useQuery } from "convex/react";
import { api } from "../convex/_generated/api";
// This automatically updates when data changes
const products = useQuery(api.shared.products.getProducts, {
storeId: "store_123"
});
// This updates in real-time
const orders = useQuery(api.customers.orders.getCustomerOrders, {
customerId: "user_123"
});
Real-time Features Implementation
1. Live Order Tracking
Copy
const OrderTracker = ({ orderId }) => {
const order = useQuery(api.customers.orders.getOrder, { orderId });
if (!order) return <div>Loading...</div>;
return (
<div className="order-tracker">
<h3>Order #{order.id}</h3>
<div className="status">
Status: <span className={order.status}>{order.status}</span>
</div>
<div className="timeline">
{order.statusHistory.map((status, index) => (
<div key={index} className={`timeline-item ${status.completed ? 'completed' : ''}`}>
<div className="timeline-marker"></div>
<div className="timeline-content">
<h4>{status.status}</h4>
<p>{status.timestamp}</p>
</div>
</div>
))}
</div>
</div>
);
};
2. Live Inventory Updates
Copy
const ProductCard = ({ productId }) => {
const product = useQuery(api.shared.products.getProductById, { productId });
if (!product) return <div>Loading...</div>;
const isInStock = product.quantity > 0;
return (
<div className={`product-card ${!isInStock ? 'out-of-stock' : ''}`}>
<h3>{product.name}</h3>
<p>Price: {product.price} AED</p>
<p className={isInStock ? 'in-stock' : 'out-of-stock'}>
{isInStock ? `${product.quantity} in stock` : 'Out of stock'}
</p>
<button
disabled={!isInStock}
onClick={() => addToCart(productId)}
>
{isInStock ? 'Add to Cart' : 'Out of Stock'}
</button>
</div>
);
};
3. Live Chat/Messaging
Copy
const ChatRoom = ({ storeId }) => {
const messages = useQuery(api.stores.queries.getStoreMessages, { storeId });
const [newMessage, setNewMessage] = useState("");
const sendMessage = useMutation(api.stores.operations.sendMessage);
const handleSend = async () => {
if (newMessage.trim()) {
await sendMessage({
storeId,
message: newMessage,
senderId: currentUser.id
});
setNewMessage("");
}
};
return (
<div className="chat-room">
<div className="messages">
{messages?.map((message) => (
<div key={message.id} className="message">
<strong>{message.senderName}:</strong> {message.text}
<span className="timestamp">{message.timestamp}</span>
</div>
))}
</div>
<div className="message-input">
<input
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type a message..."
onKeyPress={(e) => e.key === 'Enter' && handleSend()}
/>
<button onClick={handleSend}>Send</button>
</div>
</div>
);
};
Real-time Notifications
1. Push Notifications
Copy
const NotificationManager = () => {
const notifications = useQuery(api.shared.notifications.getNotifications, {
userId: currentUser.id
});
useEffect(() => {
// Request notification permission
if ('Notification' in window) {
Notification.requestPermission();
}
}, []);
useEffect(() => {
if (notifications?.length > 0) {
const latestNotification = notifications[0];
// Show browser notification
if (Notification.permission === 'granted') {
new Notification(latestNotification.title, {
body: latestNotification.body,
icon: '/logo.png'
});
}
}
}, [notifications]);
return (
<div className="notifications">
{notifications?.map((notification) => (
<div key={notification.id} className="notification">
<h4>{notification.title}</h4>
<p>{notification.body}</p>
<span className="time">{notification.timestamp}</span>
</div>
))}
</div>
);
};
2. Live Feed Updates
Copy
const LiveFeed = () => {
const feed = useQuery(api.customers.feed.getFeed, {
userId: currentUser.id,
limit: 20
});
return (
<div className="live-feed">
<h2>Live Feed</h2>
{feed?.map((item) => (
<div key={item.id} className="feed-item">
<div className="feed-header">
<img src={item.storeLogo} alt={item.storeName} />
<div>
<h3>{item.storeName}</h3>
<span className="time">{item.timestamp}</span>
</div>
</div>
<div className="feed-content">
<h4>{item.title}</h4>
<p>{item.description}</p>
{item.image && <img src={item.image} alt="Feed image" />}
</div>
<div className="feed-actions">
<button onClick={() => likeItem(item.id)}>
❤️ {item.likes}
</button>
<button onClick={() => shareItem(item.id)}>
📤 Share
</button>
</div>
</div>
))}
</div>
);
};
Real-time Analytics
1. Live Dashboard
Copy
const StoreDashboard = ({ storeId }) => {
const dashboard = useQuery(api.stores.dashboard.getDashboardOverview, { storeId });
const topProducts = useQuery(api.stores.dashboard.getTopSellingProducts, { storeId });
const recentOrders = useQuery(api.stores.dashboard.getRecentOrders, { storeId });
if (!dashboard) return <div>Loading dashboard...</div>;
return (
<div className="dashboard">
<div className="stats-grid">
<div className="stat-card">
<h3>Total Sales</h3>
<p className="stat-value">{dashboard.totalSales} AED</p>
<span className="stat-change">+{dashboard.salesGrowth}%</span>
</div>
<div className="stat-card">
<h3>Orders Today</h3>
<p className="stat-value">{dashboard.ordersToday}</p>
<span className="stat-change">+{dashboard.orderGrowth}%</span>
</div>
<div className="stat-card">
<h3>Active Products</h3>
<p className="stat-value">{dashboard.activeProducts}</p>
</div>
</div>
<div className="dashboard-sections">
<div className="top-products">
<h3>Top Selling Products</h3>
{topProducts?.map((product) => (
<div key={product.id} className="product-item">
<span>{product.name}</span>
<span>{product.sales} sold</span>
</div>
))}
</div>
<div className="recent-orders">
<h3>Recent Orders</h3>
{recentOrders?.map((order) => (
<div key={order.id} className="order-item">
<span>Order #{order.id}</span>
<span>{order.total} AED</span>
<span className={`status ${order.status}`}>{order.status}</span>
</div>
))}
</div>
</div>
</div>
);
};
Performance Optimization
1. Selective Subscriptions
Copy
// Only subscribe to data you need
const ProductList = ({ storeId, category }) => {
// This will only re-render when products in this category change
const products = useQuery(api.shared.products.getProducts, {
storeId,
category,
limit: 20
});
return (
<div>
{products?.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
);
};
2. Optimistic Updates
Copy
const useOptimisticLike = () => {
const likeProduct = useMutation(api.customers.feed.likeProduct);
const optimisticLike = async (productId: string) => {
// Optimistically update UI
setLikedProducts(prev => [...prev, productId]);
try {
await likeProduct({ productId });
} catch (error) {
// Revert on error
setLikedProducts(prev => prev.filter(id => id !== productId));
}
};
return { optimisticLike };
};
Best Practices
- Use selective subscriptions - only subscribe to data you need
- Handle loading states - always check for undefined
- Implement error boundaries - handle subscription errors gracefully
- Use optimistic updates - for better perceived performance
- Debounce rapid updates - to avoid excessive re-renders
- Clean up subscriptions - when components unmount
Real-time features work automatically with Convex - no additional setup required! Just use
useQuery and your data will update in real-time.