Skip to main content

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

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

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

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

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

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

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

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

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

// 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

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

  1. Use selective subscriptions - only subscribe to data you need
  2. Handle loading states - always check for undefined
  3. Implement error boundaries - handle subscription errors gracefully
  4. Use optimistic updates - for better perceived performance
  5. Debounce rapid updates - to avoid excessive re-renders
  6. 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.