Anti-Pattern 1: Broadcasting Every Keystroke
Novice thinking: "Send every change immediately for real-time feel"
Problem: Network floods with tiny messages, poor performance.
Wrong approach:
```typescript
// β Sends message on every keystroke
function Editor() {
const handleChange = (text: string) => {
socket.emit('text-change', { text }); // Every keystroke!
};
return
}
```
Why wrong: 100 WPM typing = 500 messages/minute = network congestion.
Correct approach:
```typescript
// β
Batches changes every 200ms
function Editor() {
const [pendingChanges, setPendingChanges] = useState([]);
useEffect(() => {
const interval = setInterval(() => {
if (pendingChanges.length > 0) {
socket.emit('text-batch', { changes: pendingChanges });
setPendingChanges([]);
}
}, 200);
return () => clearInterval(interval);
}, [pendingChanges]);
const handleChange = (change: Change) => {
setPendingChanges(prev => [...prev, change]);
};
return ;
}
```
Impact: 500 messages/minute β 5 messages/second (90% reduction).
---
Anti-Pattern 2: No Conflict Resolution Strategy
Problem: Concurrent edits cause data loss or corruption.
Symptom: Users see their changes disappear, documents become inconsistent.
Wrong approach:
```typescript
// β Last write wins, overwrites concurrent changes
socket.on('text-change', ({ userId, text }) => {
setDocument(text); // Loses concurrent edits!
});
```
Why wrong: If User A and B edit simultaneously, one change is lost.
Correct approach (OT):
```typescript
// β
Operational Transform for text
import { TextOperation } from 'ot.js';
socket.on('operation', ({ userId, operation, revision }) => {
const transformed = transformOperation(
operation,
pendingOperations,
revision
);
applyOperation(transformed);
incrementRevision();
});
function transformOperation(
incoming: Operation,
pending: Operation[],
baseRevision: number
): Operation {
// Transform incoming against pending operations
let transformed = incoming;
for (const op of pending) {
transformed = TextOperation.transform(transformed, op)[0];
}
return transformed;
}
```
Correct approach (CRDT):
```typescript
// β
CRDT for JSON objects
import * as Y from 'yjs';
const ydoc = new Y.Doc();
const ytext = ydoc.getText('document');
// Automatically handles conflicts
ytext.insert(0, 'Hello');
// Sync with peers
const provider = new WebsocketProvider('ws://localhost:1234', 'room', ydoc);
```
Impact: Concurrent edits merge correctly, no data loss.
---
Anti-Pattern 3: Not Handling Disconnections
Problem: User goes offline, loses work or sees stale state.
Wrong approach:
```typescript
// β No offline handling
socket.on('disconnect', () => {
console.log('Disconnected'); // That's it?!
});
```
Why wrong: Pending changes lost, no reconnection strategy, bad UX.
Correct approach:
```typescript
// β
Queue changes offline, sync on reconnect
const [isOnline, setIsOnline] = useState(true);
const [offlineQueue, setOfflineQueue] = useState([]);
socket.on('disconnect', () => {
setIsOnline(false);
showToast('Offline - changes will sync when reconnected');
});
socket.on('connect', () => {
setIsOnline(true);
// Send queued changes
if (offlineQueue.length > 0) {
socket.emit('sync-offline-changes', { changes: offlineQueue });
setOfflineQueue([]);
}
});
const handleChange = (change: Change) => {
if (isOnline) {
socket.emit('change', change);
} else {
setOfflineQueue(prev => [...prev, change]);
}
};
```
Timeline context:
- 2015: Offline-first apps rare
- 2020: PWAs make offline UX standard
- 2024: Users expect seamless offline editing
---
Anti-Pattern 4: Client-Only State Sync
Problem: No server authority, clients get out of sync.
Wrong approach:
```typescript
// β Clients broadcast to each other directly
socket.on('peer-change', ({ userId, change }) => {
applyChange(change); // No validation, no server state
});
```
Why wrong: Malicious client can send invalid data, no recovery from desync.
Correct approach:
```typescript
// β
Server is source of truth
// Client
socket.emit('operation', { operation, clientRevision });
socket.on('ack', ({ serverRevision }) => {
if (serverRevision !== expectedRevision) {
// Desync detected, request full state
socket.emit('request-full-state');
}
});
// Server
io.on('connection', (socket) => {
socket.on('operation', ({ operation, clientRevision }) => {
// Validate operation
if (!isValid(operation)) {
socket.emit('error', { message: 'Invalid operation' });
return;
}
// Apply to server state
const serverRevision = applyOperation(operation);
// Broadcast to all clients
io.emit('operation', { operation, serverRevision });
});
});
```
Impact: Data integrity guaranteed, can recover from client bugs.
---
Anti-Pattern 5: No Presence Awareness
Problem: Users can't see who's editing what, causing edit conflicts.
Symptom: Two people editing same section unknowingly.
Wrong approach:
```typescript
// β No awareness of other users
function Editor() {
return ; // Flying blind!
}
```
Correct approach:
```typescript
// β
Show active users and cursors
import { usePresence } from './usePresence';
function Editor() {
const { users, updateCursor } = usePresence();
const handleCursorMove = (position: number) => {
socket.emit('cursor-move', { userId: myId, position });
};
return (
{/ Show who's online /}
{/ Show remote cursors /}
content={content}
cursors={users.map(u => u.cursor)}
onCursorMove={handleCursorMove}
/>
);
}
```
Features:
- Active user list with avatars
- Cursor positions color-coded by user
- Selection ranges highlighted
- "User X is typing..." indicators
---