I am creating a food ordering app, I want to use SSE to sent order approval request to the sellers using SSE when a new order is made.
I am using starlette SSE and React native sse package, but i am getting the same message multiple times even though the yield statement is executed only once. That means the backend is sending the message once but the frontend is recieving the same message multiple times.
Here is my code for Starlette SSE
@router.get("/seller-orders")
async def message_stream(
request: Request,
seller: SellerTable = Depends(current_seller),
session: Session = Depends(get_session),
):
async def event_generator():
while True:
if await request.is_disconnected():
logger.debug("Request disconnected")
break
pending_orders = get_pending_orders(seller=seller, session=session)
if pending_orders:
print("--order request list", pending_orders)
yield LiveSyncMessage(pending_orders=pending_orders).model_dump_json()
print("ack-and-deleting-data")
seller_order_request_db.delete_item(seller.id)
await asyncio.sleep(MESSAGE_STREAM_DELAY)
return EventSourceResponse(event_generator())
Here is my React native EventSource implementation
import { create } from "zustand";
import AsyncStorage from "@react-native-async-storage/async-storage";
import EventSource from "react-native-sse";
import { PendingOrder } from "../models/PendingOrder";
import { TOKEN_STORAGE_KEY } from "../auth/AuthStore";
interface PendingOrdersStoreState {
pendingOrders: PendingOrder[];
setPendingOrders: (orders: PendingOrder[]) => void;
connectToPendingOrdersSSE: () => void;
}
// Define the Zustand store
const usePendingOrdersStore = create<PendingOrdersStoreState>((set) => ({
pendingOrders: [],
setPendingOrders: (orders) => set({ pendingOrders: orders }),
connectToPendingOrdersSSE: async () => {
const userToken = await AsyncStorage.getItem(TOKEN_STORAGE_KEY);
if (!userToken) {
console.error("User token not found");
return;
}
const url = `http://localhost:8000/livesync/seller-orders?access_token=${userToken}`;
const eventSource = new EventSource(url, {
headers: {
Authorization: {
toString: function () {
return "Bearer " + userToken;
},
},
},
});
console.log(eventSource);
eventSource.addEventListener("open", (event) => {
console.log("Open SSE connection.");
});
eventSource.addEventListener("message", (event) => {
console.log("Received data:", event.data);
try {
const responseData = JSON.parse(event.data);
const pendingOrders = responseData.pending_orders;
console.log("Parsed data:", pendingOrders);
set({ pendingOrders: pendingOrders });
} catch (error) {
console.error("Error parsing JSON:", error);
}
});
eventSource.addEventListener("error", (event) => {
if (event.type === "error") {
console.error("Connection error:", event.message);
} else if (event.type === "exception") {
console.error("Error:", event.message, event.error);
}
});
eventSource.addEventListener("close", (event) => {
console.log("Close SSE connection.");
});
return () => {
eventSource.close();
};
},
}));
export default usePendingOrdersStore;
I tried different packages, closing the listeners, cahnging the logic in sse backend
Here is my output in frontend
WARN (ADVICE) View #95 of type ABI49_0_0RCTView has a shadow set but cannot calculate shadow efficiently. Consider setting a background color to fix this, or apply the shadow to a more specific component.
LOG Received data: {"pending_orders":[{"buyer_name":null,"buyer_profile_image_url":null,"dish_id":1,"dish_title":"Paneer Tikka","dish_price":455,"dish_image_url":null,"order_portions":1,"available_portions":5,"is_pickup":true,"is_delivery":false}]}
LOG Parsed data: [{"available_portions": 5, "buyer_name": null, "buyer_profile_image_url": null, "dish_id": 1, "dish_image_url": null, "dish_price": 455, "dish_title": "Paneer Tikka", "is_delivery": false, "is_pickup": true, "order_portions": 1}]
LOG Received data: {"pending_orders":[{"buyer_name":null,"buyer_profile_image_url":null,"dish_id":1,"dish_title":"Paneer Tikka","dish_price":455,"dish_image_url":null,"order_portions":1,"available_portions":5,"is_pickup":true,"is_delivery":false}]}
LOG Parsed data: [{"available_portions": 5, "buyer_name": null, "buyer_profile_image_url": null, "dish_id": 1, "dish_image_url": null, "dish_price": 455, "dish_title": "Paneer Tikka", "is_delivery": false, "is_pickup": true, "order_portions": 1}]
LOG Received data: {"pending_orders":[{"buyer_name":null,"buyer_profile_image_url":null,"dish_id":1,"dish_title":"Paneer Tikka","dish_price":455,"dish_image_url":null,"order_portions":1,"available_portions":5,"is_pickup":true,"is_delivery":false}]}
LOG Parsed data: [{"available_portions": 5, "buyer_name": null, "buyer_profile_image_url": null, "dish_id": 1, "dish_image_url": null, "dish_price": 455, "dish_title": "Paneer Tikka", "is_delivery": false, "is_pickup": true, "order_portions": 1}]
backend