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.