DEV Community

jayanti-neu
jayanti-neu

Posted on

πŸš› Freight Tracker Frontend β€” Phase 2 & Phase 3 Recap

React + TypeScript + TailwindCSS + WebSockets + Zustand + Leaflet.js

🧭 Overview

The frontend of Freight Tracker has come a long way! In Phase 2 and Phase 3, we shifted from a static layout to a dynamic, interactive logistics dashboard powered by live updates and rich UI components. In this post, I'll walk through the main features implemented, how we handled state management and live updates, and some challenges I encountered (and solved).

πŸ“¦ Phase 2: CRUD Pages, State Management & Styling

βœ… 1. Shipment Type Definitions

We created proper TypeScript types (Shipment, ShipmentStatus, ShipmentStatsDTO, etc.) to mirror the backend schema and ensure type-safe code across components.

export type ShipmentStatus = "PENDING" | "IN_TRANSIT" | "DELIVERED" | "CANCELLED";

export interface Shipment {
  id: number;
  origin: string;
  destination: string;
  status: ShipmentStatus;
  trackingNumber: string;
  carrier?: string;
  priority?: "LOW" | "MEDIUM" | "HIGH";
  lastUpdatedTime: string;
}
Enter fullscreen mode Exit fullscreen mode

🧠 2. Global State with Zustand

Zustand was introduced to maintain and manipulate shipment data globally. This allowed pages like the list, details, and map view to share consistent data without prop drilling.

const useShipmentStore = create((set) => ({
  byId: {},
  upsert: (shipment) => set((state) => ({
    byId: { ...state.byId, [shipment.id]: shipment }
  })),
  bulkLoad: (shipments) => set({
    byId: Object.fromEntries(shipments.map(s => [s.id, s]))
  }),
}));
Enter fullscreen mode Exit fullscreen mode

πŸ–ΌοΈ 3. Basic Pages with Routing

We added multiple pages using react-router-dom:

  • / β€” HomePage
  • /shipments β€” ShipmentsPage
  • /shipments/:id β€” ShipmentDetailsPage
  • /dashboard β€” MapDashboardPage (added in Phase 3)

Routing was kept simple, with a layout container and top navigation.

πŸ–οΈ 4. Styling with TailwindCSS

All pages were styled with Tailwind for quick, consistent design. We used colors, spacing, typography, and component states (like animate-pulse on loading) to make the UI pleasant and professional.

⚑ Phase 3: WebSocket Updates, Maps, and Visualization

🌐 1. Real-Time WebSocket Integration

Using @stomp/stompjs and a WebSocket backend endpoint (/ws), we added live updates to shipments. Any change to a shipment in the backend triggers a broadcast message, which is parsed and upserts the new shipment into Zustand.

stompClient?.subscribe("/topic/shipments", (message) => {
  const shipment: Shipment = JSON.parse(message.body);
  useShipmentStore.getState().upsert(shipment);
});
Enter fullscreen mode Exit fullscreen mode

This ensures all views (including the map) reflect changes instantly β€” no need to refresh!

πŸ—ΊοΈ 2. Interactive Map with Leaflet

We created a rich dashboard map to visualize routes between origin and destination cities. Highlights:

  • Leaflet.js with react-leaflet for the map
  • Geocoding city names using OpenCage API
  • Route lines colored based on shipment status
  • Markers with popups for extra detail
<Polyline positions={[originCoords, destinationCoords]} color={getColorForStatus(shipment.status)}>
  <Popup>
    <strong>Tracking:</strong> {shipment.trackingNumber}
    <br />
    <strong>Status:</strong> {shipment.status}
  </Popup>
</Polyline>
Enter fullscreen mode Exit fullscreen mode

We also cached geocode results in localStorage to avoid repeated lookups.

πŸ’‘ 3. State-Driven Map Updates

Instead of fetching shipment data directly in the map page, we relied on Zustand. However, we included a fallback to getShipments() on first load in case the store was still empty. We watched Object.values(byId).length to trigger re-renders when shipments were updated.

useEffect(() => {
  const shipments = Object.values(byId);
  if (shipments.length === 0) {
    const fresh = await getShipments();
    bulkLoad(fresh);
  }
  ...
}, [byId]);
Enter fullscreen mode Exit fullscreen mode

This worked hand-in-hand with WebSocket updates β€” ensuring the map reflects changes as soon as they happen.

🚦 4. Small Optimizations

  • Loading indicator while routes are geocoded
  • Conditional polyline coloring
  • Map centering and zoom configuration
  • Clean Leaflet icon setup
  • Using React.Fragment with stable key props for rendering

🧠 Lessons Learned

  • Zustand is great for lightweight state management with zero boilerplate
  • WebSocket integration with React is smooth with STOMP + Zustand
  • It's important to debounce or cache expensive calls like geocoding
  • Use localStorage smartly to persist data across sessions
  • Routing doesn't cause component remounts by default β€” understand how effects work in SPA frameworks like React!

πŸ“Œ Next Steps

  • Add authentication and role-based views (client/admin separation)
  • Real-time truck location tracking
  • Better filtering and sorting options
  • UI polish and animations
  • Backend caching for geocodes (to avoid using frontend localStorage in production)

Top comments (0)