Data Flow Communication Patterns
1. Unidirectional Flux Redux Architecture
Concept
Pattern
Description
Benefit
Flux Architecture
Action → Dispatcher → Store → View
One-way data flow where actions trigger state changes, views subscribe to updates
Predictable state changes, easier debugging, time-travel capability
Actions
{ type, payload }
Plain objects describing what happened, dispatched to modify state
Serializable events, action log for debugging, middleware intercept
Reducers
(state, action) => newState
Pure functions calculating next state based on current state and action
Testable, predictable, composable state transitions
Store
createStore(reducer)
Single source of truth holding entire application state tree
Centralized state, subscription mechanism, middleware pipeline
Middleware
store => next => action
Pipeline for side effects, logging, async actions (thunk, saga)
Separates business logic from UI, reusable effects
Example: Flux Pattern with Redux
// Action Types
const INCREMENT = 'counter/increment' ;
const FETCH_USER = 'user/fetch' ;
// Action Creators
const increment = () => ({ type: INCREMENT });
const fetchUserAsync = ( id ) => async ( dispatch ) => {
dispatch ({ type: FETCH_USER + '/pending' });
try {
const user = await api. getUser (id);
dispatch ({ type: FETCH_USER + '/fulfilled' , payload: user });
} catch (error) {
dispatch ({ type: FETCH_USER + '/rejected' , payload: error });
}
};
// Reducer (Pure Function)
function counterReducer ( state = { value: 0 }, action ) {
switch (action.type) {
case INCREMENT :
return { ... state, value: state.value + 1 };
default :
return state;
}
}
// Store with Middleware
import { createStore, applyMiddleware } from 'redux' ;
import thunk from 'redux-thunk' ;
const store = createStore (counterReducer, applyMiddleware (thunk));
// Unidirectional Flow
// View dispatches action
store. dispatch ( increment ());
// Reducer produces new state
// Store notifies subscribers
// View re-renders with new state
Flux Principles
Single Direction: Data flows only one way (Action → Store → View)
Predictability: Same actions + same state = same result
Centralized State: One store, one source of truth
Immutability: State never mutated directly, always replaced
2. Event-Driven Pub Sub Implementation
Concept
Pattern
Description
Use Case
Event Emitter
EventEmitter
Publish-subscribe pattern for decoupled component communication
Cross-component events, micro-frontend communication
Custom Events
new CustomEvent()
Browser native event system for inter-component messaging
Web components, widget communication, analytics tracking
Event Bus
eventBus.emit/on
Global message bus for application-wide events
Global notifications, cross-feature communication
RxJS Observables
Subject, BehaviorSubject
Reactive streams for event handling with operators (map, filter, debounce)
Complex event flows, async operations, real-time updates
Message Queue
postMessage()
Cross-origin communication between windows, iframes, workers
Micro-frontends, iframe isolation, web workers
Event Emitter Pattern
// Simple Event Emitter
class EventEmitter {
constructor () {
this .events = {};
}
on ( event , listener ) {
if ( ! this .events[event]) {
this .events[event] = [];
}
this .events[event]. push (listener);
// Return unsubscribe function
return () => this . off (event, listener);
}
off ( event , listener ) {
if ( ! this .events[event]) return ;
this .events[event] = this .events[event]
. filter ( l => l !== listener);
}
emit ( event , data ) {
if ( ! this .events[event]) return ;
this .events[event]. forEach ( listener =>
listener (data)
);
}
once ( event , listener ) {
const onceWrapper = ( data ) => {
listener (data);
this . off (event, onceWrapper);
};
this . on (event, onceWrapper);
}
}
// Usage
const bus = new EventEmitter ();
bus. on ( 'user:login' , ( user ) => {
console. log ( 'User logged in:' , user);
});
bus. emit ( 'user:login' , { id: 1 , name: 'John' });
RxJS Observable Pattern
import { Subject, BehaviorSubject } from 'rxjs' ;
import { debounceTime, distinctUntilChanged } from 'rxjs/operators' ;
// Subject - hot observable
const userEvents$ = new Subject ();
userEvents$. subscribe ( event => {
console. log ( 'User event:' , event);
});
userEvents$. next ({ type: 'click' , target: 'button' });
// BehaviorSubject - stores current value
const currentUser$ = new BehaviorSubject ( null );
currentUser$. subscribe ( user => {
console. log ( 'Current user:' , user);
});
currentUser$. next ({ id: 1 , name: 'John' });
// Search input with debounce
const searchInput$ = new Subject ();
searchInput$
. pipe (
debounceTime ( 300 ),
distinctUntilChanged ()
)
. subscribe ( query => {
console. log ( 'Search:' , query);
// Trigger API call
});
// Emit search queries
searchInput$. next ( 'react' );
searchInput$. next ( 'redux' ); // Debounced
Example: Custom Events for Web Components
// Dispatch custom event
class UserCard extends HTMLElement {
connectedCallback () {
this . addEventListener ( 'click' , () => {
this . dispatchEvent ( new CustomEvent ( 'user-selected' , {
detail: { userId: this .dataset.userId },
bubbles: true ,
composed: true // Cross shadow DOM boundaries
}));
});
}
}
// Listen for custom event
document. addEventListener ( 'user-selected' , ( event ) => {
console. log ( 'User selected:' , event.detail.userId);
// Load user details, navigate, etc.
});
// React integration
function App () {
useEffect (() => {
const handler = ( e ) => console. log (e.detail);
document. addEventListener ( 'user-selected' , handler);
return () => document. removeEventListener ( 'user-selected' , handler);
}, []);
}
Caution: Event-driven patterns can lead to hidden dependencies
and difficult debugging. Use typed events, clear naming conventions, and consider Redux DevTools for observable
tracking.
3. Real-time WebSocket Socket.io
Technology
API
Description
Use Case
WebSocket API
new WebSocket(url)
Native browser API for full-duplex bidirectional communication over TCP
Real-time apps, low latency, persistent connection
Socket.IO
io()
Library with automatic reconnection, fallbacks, rooms, namespaces
Chat apps, live collaboration, multiplayer games
Heartbeat/Ping-Pong
socket.ping()
Keep-alive mechanism to detect disconnections early
Connection health monitoring, automatic reconnection
Rooms/Channels
socket.join(room)
Broadcast messages to specific groups of connected clients
Chat rooms, game lobbies, team workspaces
Binary Support
ArrayBuffer, Blob
Send binary data efficiently (images, files, audio)
File transfers, video streaming, game state sync
Example: WebSocket and Socket.IO Implementation
// Native WebSocket
const ws = new WebSocket ( 'wss://example.com/socket' );
ws. onopen = () => {
console. log ( 'Connected' );
ws. send ( JSON . stringify ({ type: 'subscribe' , channel: 'updates' }));
};
ws. onmessage = ( event ) => {
const data = JSON . parse (event.data);
console. log ( 'Message:' , data);
};
ws. onerror = ( error ) => {
console. error ( 'WebSocket error:' , error);
};
ws. onclose = () => {
console. log ( 'Disconnected' );
// Implement reconnection logic
setTimeout (() => reconnect (), 1000 );
};
// Socket.IO Client
import { io } from 'socket.io-client' ;
const socket = io ( 'https://example.com' , {
reconnection: true ,
reconnectionDelay: 1000 ,
reconnectionAttempts: 5 ,
transports: [ 'websocket' , 'polling' ] // Fallback to polling
});
socket. on ( 'connect' , () => {
console. log ( 'Connected:' , socket.id);
});
socket. on ( 'message' , ( data ) => {
console. log ( 'Received:' , data);
});
socket. emit ( 'chat-message' , { room: 'general' , text: 'Hello!' });
// Join room
socket. emit ( 'join-room' , 'room-123' );
// React Hook for Socket.IO
function useSocket ( url ) {
const [ socket , setSocket ] = useState ( null );
const [ isConnected , setIsConnected ] = useState ( false );
useEffect (() => {
const newSocket = io (url);
newSocket. on ( 'connect' , () => setIsConnected ( true ));
newSocket. on ( 'disconnect' , () => setIsConnected ( false ));
setSocket (newSocket);
return () => newSocket. close ();
}, [url]);
return { socket, isConnected };
}
WebSocket vs Socket.IO
Feature
WebSocket
Socket.IO
Protocol
Native WS protocol
Custom protocol
Reconnection
Manual implementation
Automatic
Fallback
None
Long polling
Rooms
Manual
Built-in
Bundle Size
0KB (native)
~50KB
Best Practices
Use wss:// (secure WebSocket) in production
Implement exponential backoff for reconnection
Validate and sanitize all incoming messages
Set connection timeout and heartbeat interval
Use binary format (MessagePack) for efficiency
Handle offline state gracefully with queue
Monitor connection status in UI
Close connections properly on unmount
4. Server-Sent Events SSE Streaming
Concept
API
Description
Advantage
Server-Sent Events
new EventSource(url)
One-way server-to-client push over HTTP, automatic reconnection
Simpler than WebSocket for one-way updates, HTTP/2 compatible
Text-Based Protocol
data: message\n\n
UTF-8 text format with event types, IDs, retry configuration
Human-readable, easy debugging, works through proxies
Auto Reconnection
retry: 3000
Browser automatically reconnects with last event ID
Resilient connections without custom retry logic
Event Types
event: custom-event
Named events for different message types from single endpoint
Type-safe event handling, multiple event listeners
HTTP Streaming
Transfer-Encoding: chunked
Keeps HTTP connection open, sends chunks incrementally
Works with existing HTTP infrastructure, CDN support
Example: Server-Sent Events Implementation
// Client: EventSource API
const eventSource = new EventSource ( '/api/events' );
// Default message event
eventSource. onmessage = ( event ) => {
const data = JSON . parse (event.data);
console. log ( 'Message:' , data);
};
// Custom event types
eventSource. addEventListener ( 'user-update' , ( event ) => {
const user = JSON . parse (event.data);
console. log ( 'User updated:' , user);
});
eventSource. addEventListener ( 'notification' , ( event ) => {
const notification = JSON . parse (event.data);
showToast (notification.message);
});
// Connection lifecycle
eventSource. onopen = () => {
console. log ( 'SSE connection opened' );
};
eventSource. onerror = ( error ) => {
console. error ( 'SSE error:' , error);
if (eventSource.readyState === EventSource. CLOSED ) {
console. log ( 'Connection closed' );
}
};
// Close connection
eventSource. close ();
// Server: Node.js/Express
app. get ( '/api/events' , ( req , res ) => {
res. setHeader ( 'Content-Type' , 'text/event-stream' );
res. setHeader ( 'Cache-Control' , 'no-cache' );
res. setHeader ( 'Connection' , 'keep-alive' );
// Send initial message
res. write ( 'data: {"message": "Connected"} \n\n ' );
// Send periodic updates
const interval = setInterval (() => {
res. write ( `data: ${ JSON . stringify ({ time: Date . now () }) } \n\n ` );
}, 1000 );
// Custom event with ID
res. write ( 'event: user-update \n ' );
res. write ( 'id: 123 \n ' );
res. write ( 'data: {"name": "John", "status": "online"} \n\n ' );
// Set retry timeout (milliseconds)
res. write ( 'retry: 10000 \n\n ' );
// Cleanup on client disconnect
req. on ( 'close' , () => {
clearInterval (interval);
res. end ();
});
});
SSE vs WebSocket
Feature
SSE
WebSocket
Direction
Server → Client
Bidirectional
Protocol
HTTP
WS
Reconnection
Automatic
Manual
Data Format
Text only
Text + Binary
Complexity
Simple
Complex
SSE Use Cases
Live notifications and alerts
Stock price tickers
News feed updates
Server log streaming
Progress indicators for long tasks
Social media live feeds
Sports scores and updates
IoT sensor data monitoring
When to use SSE: Perfect for one-way server push scenarios.
Simpler than WebSocket, works through firewalls, automatic reconnection, and HTTP/2 multiplexing support.
5. GraphQL Subscription Live Updates
Concept
Implementation
Description
Use Case
GraphQL Subscriptions
subscription { ... }
Real-time GraphQL queries that push updates when data changes
Live data feeds, collaborative editing, chat applications
Transport Layer
WebSocket, SSE
Subscriptions use WebSocket or SSE for bidirectional communication
Flexible transport based on requirements
Apollo Client
useSubscription()
React hook for GraphQL subscriptions with automatic reconnection
Declarative real-time data in React components
Pub/Sub Pattern
pubsub.publish()
Server-side publish-subscribe for triggering subscription updates
Multi-client updates, event-driven architecture
Filtering
withFilter()
Server-side filtering to send updates only to relevant subscribers
Optimize bandwidth, user-specific updates
Example: GraphQL Subscription Implementation
// Schema Definition
type Subscription {
messageAdded(roomId: ID!): Message
userStatusChanged(userId: ID!): UserStatus
notificationReceived: Notification
}
type Message {
id: ID!
text: String!
user: User!
timestamp: String!
}
// Server: GraphQL Resolver
import { PubSub, withFilter } from 'graphql-subscriptions' ;
const pubsub = new PubSub ();
const MESSAGE_ADDED = 'MESSAGE_ADDED' ;
const resolvers = {
Mutation: {
addMessage : async ( _ , { roomId , text }, { user }) => {
const message = {
id: generateId (),
text,
user,
roomId,
timestamp: new Date (). toISOString ()
};
await saveMessage (message);
// Publish to subscribers
pubsub. publish ( MESSAGE_ADDED , { messageAdded: message });
return message;
}
},
Subscription: {
messageAdded: {
// Filter: only send to users in the same room
subscribe: withFilter (
() => pubsub. asyncIterator ([ MESSAGE_ADDED ]),
( payload , variables ) => {
return payload.messageAdded.roomId === variables.roomId;
}
)
}
}
};
// Client: Apollo Client Setup
import { split, HttpLink, ApolloClient, InMemoryCache } from '@apollo/client' ;
import { GraphQLWsLink } from '@apollo/client/link/subscriptions' ;
import { getMainDefinition } from '@apollo/client/utilities' ;
import { createClient } from 'graphql-ws' ;
const httpLink = new HttpLink ({ uri: 'http://localhost:4000/graphql' });
const wsLink = new GraphQLWsLink (
createClient ({ url: 'ws://localhost:4000/graphql' })
);
// Split based on operation type
const splitLink = split (
({ query }) => {
const definition = getMainDefinition (query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink
);
const client = new ApolloClient ({
link: splitLink,
cache: new InMemoryCache ()
});
Example: React Component with Subscription
import { useSubscription, gql, useMutation } from '@apollo/client' ;
const MESSAGE_SUBSCRIPTION = gql `
subscription OnMessageAdded($roomId: ID!) {
messageAdded(roomId: $roomId) {
id
text
user { name }
timestamp
}
}
` ;
const ADD_MESSAGE = gql `
mutation AddMessage($roomId: ID!, $text: String!) {
addMessage(roomId: $roomId, text: $text) {
id
text
}
}
` ;
function ChatRoom ({ roomId }) {
const [ messages , setMessages ] = useState ([]);
// Subscription
const { data , loading } = useSubscription ( MESSAGE_SUBSCRIPTION , {
variables: { roomId },
onData : ({ data }) => {
setMessages ( prev => [ ... prev, data.data.messageAdded]);
}
});
// Mutation
const [ addMessage ] = useMutation ( ADD_MESSAGE );
const handleSend = ( text ) => {
addMessage ({ variables: { roomId, text } });
};
return (
< div >
{messages. map ( msg => (
< div key = {msg.id}>{msg.user.name}: {msg.text}</ div >
))}
< input onSubmit = {( e ) => handleSend (e.target.value)} />
</ div >
);
}
GraphQL Subscription Benefits
Type Safety: Strongly typed real-time data with schema validation
Selective Fields: Request only needed fields, reduce bandwidth
Unified API: Queries, mutations, and subscriptions in one endpoint
Client Control: Client decides what to subscribe to and when
6. React Props Context Drilling Solution
Pattern
Solution
Description
When to Use
Prop Drilling Problem
Parent → Child → GrandChild
Passing props through intermediate components that don't need them
Recognize as code smell when props pass through 3+ levels
Component Composition
children, render props
Pass components as children instead of data, invert dependencies
First solution to try, maintains component isolation
Context API
createContext + Provider
Share data across component tree without explicit props
Theme, auth, locale - infrequently changing data
State Management
Redux, Zustand, Jotai
External state store accessible from any component
Complex state logic, frequent updates, shared state
Custom Hooks
useCustomHook()
Encapsulate data fetching and state logic in reusable hooks
Share logic without sharing state, API abstractions
Problem: Prop Drilling
// Bad: Props drilled through components
function App () {
const [ user , setUser ] = useState ( null );
return < Dashboard user = {user} />;
}
function Dashboard ({ user }) {
// Dashboard doesn't use user
return < Sidebar user = {user} />;
}
function Sidebar ({ user }) {
// Sidebar doesn't use user
return < UserProfile user = {user} />;
}
function UserProfile ({ user }) {
// Finally uses user
return < div >{user.name}</ div >;
}
Solution 1: Composition
// Good: Composition pattern
function App () {
const [ user , setUser ] = useState ( null );
return (
< Dashboard >
< Sidebar >
< UserProfile user = {user} />
</ Sidebar >
</ Dashboard >
);
}
function Dashboard ({ children }) {
return < div className = "dashboard" >{children}</ div >;
}
function Sidebar ({ children }) {
return < aside >{children}</ aside >;
}
function UserProfile ({ user }) {
return < div >{user.name}</ div >;
}
Example: Context API Solution
// Solution 2: Context API
import { createContext, useContext } from 'react' ;
const UserContext = createContext ( null );
function App () {
const [ user , setUser ] = useState ( null );
return (
< UserContext.Provider value = {user}>
< Dashboard />
</ UserContext.Provider >
);
}
function Dashboard () {
// No user prop needed
return < Sidebar />;
}
function Sidebar () {
// No user prop needed
return < UserProfile />;
}
function UserProfile () {
const user = useContext (UserContext);
return < div >{user.name}</ div >;
}
// Solution 3: Custom Hook
function useUser () {
const context = useContext (UserContext);
if ( ! context) {
throw new Error ( 'useUser must be used within UserProvider' );
}
return context;
}
function UserProfile () {
const user = useUser (); // Clean API
return < div >{user.name}</ div >;
}
Decision Tree
Scenario
Solution
1-2 levels deep
Pass props directly
Layout composition
Component composition
Infrequent updates
Context API
Frequent updates
State management library
Logic reuse
Custom hooks
Anti-Patterns to Avoid
Using Context for frequently changing values
Creating one giant context for entire app
Drilling props more than 3 levels deep
Passing callbacks through many levels
Using Redux for truly local state
Creating context without custom hook
Not memoizing context values
Performance Note: Context API causes all consumers to re-render when value changes. For
high-frequency updates, use state management libraries with selector functions
(Zustand, Jotai) or split contexts by update frequency.