Building Real-Time Social Delivery in Expo
When we created Zoomies under the XRide Labs umbrella, the mission was simple: build a social platform for motorcyclists that felt instantly responsive. Riders needed to see each other on a live map, chat in real-time, and drop pins that would propagate across the network in milliseconds.
To achieve this, we combined the cross-platform power of Expo with the predictable state management of Redux Toolkit.
1. Expo Router: The Next.js of Mobile
If you've used React Native before 2024, you've suffered the wrath of React Navigation configs. Expo Router completely flipped the script by bringing file-based routing to mobile.
Instead of registering screens in a massive navigator file:
// app/(tabs)/map.tsx
import { useLocalSearchParams } from "expo-router";
export default function MapScreen() {
const { eventId } = useLocalSearchParams();
return (
<View style={styles.container}>
<LiveRiderMap eventId={eventId} />
</View>
);
}2. Redux as a Socket Consumer
Sockets constantly push thousands of events. Rather than passing state down the component tree or relying exclusively on Context (which would trigger full-app renders on every socket push), we bound socket.io-client straight into Redux slices.
// store/ridersSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
interface RiderState {
locations: Record<string, { lat: number; lng: number }>;
}
const initialState: RiderState = { locations: {} };
const ridersSlice = createSlice({
name: "riders",
initialState,
reducers: {
updateRiderLocation: (
state,
action: PayloadAction<{ id: string; lat: number; lng: number }>,
) => {
// Redux Toolkit uses Immer, allowing us to "mutate" state directly
state.locations[action.payload.id] = {
lat: action.payload.lat,
lng: action.payload.lng,
};
},
},
});3. Batched Updates
If you have 100 riders emitting location updates every 3 seconds, that's 33 Redux actions dispatched per second. To prevent the UI from shuddering, we implemented a custom batched middleware that collects socket messages and dispatches them in a single chunk every 500ms.
let locationQueue = [];
socket.on("rider_move", (data) => {
locationQueue.push(data);
});
setInterval(() => {
if (locationQueue.length > 0) {
store.dispatch(batchUpdateRiders(locationQueue));
locationQueue = [];
}
}, 500);Conclusion
Building Zoomies proved that Expo is no longer just for "toys". With sensible architectural patterns, Redux state batching, and native rendering through Expo Router, you can build heavy, real-time spatial applications without ever touching Xcode or Android Studio.