Real-time UI Patterns
Apply optimistic updates for instant feedback, use local queuing when offline, and diff incoming data to update only changed items in the UI.
Real-time UI Patterns is a free Frontend Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Frontend Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Real-time Means Real-feel UX
Real-time is more than fast — it's responsive, predictable, and forgiving of latency. Three core patterns: optimistic updates, local queuing, and smart diffing.
Optimistic Updates
Update the UI immediately on user action, before the server confirms. Roll back if the server rejects. Makes apps feel instant.
async function likePost(id) {
// 1. Optimistically update UI:
setLikes(l => ({ ...l, [id]: (l[id] || 0) + 1 }));
setLiked(prev => ({ ...prev, [id]: true }));
try {
await fetch(`/api/posts/${id}/like`, { method: 'POST' });
} catch (err) {
// 2. Rollback on failure:
setLikes(l => ({ ...l, [id]: (l[id] || 1) - 1 }));
setLiked(prev => ({ ...prev, [id]: false }));
toast.error('Could not like — please retry');
}
}Optimistic with Temporary IDs
For created items, give them a temporary ID locally. Replace with the server's real ID when the response arrives.
async function addComment(text) {
const tempId = `tmp-${Date.now()}`;
setComments(c => [...c, { id: tempId, text, pending: true }]);
try {
const saved = await fetch('/api/comments', {
method: 'POST',
body: JSON.stringify({ text })
}).then(r => r.json());
setComments(c => c.map(m => m.id === tempId ? saved : m));
} catch (err) {
setComments(c => c.map(m => m.id === tempId ? { ...m, error: true } : m));
}
}Local Queuing When Offline
When the network is down, queue actions locally and replay when it returns. The user keeps working.
const queue = JSON.parse(localStorage.getItem('queue') || '[]');
function enqueue(action) {
queue.push(action);
localStorage.setItem('queue', JSON.stringify(queue));
trySend();
}
async function trySend() {
while (queue.length && navigator.onLine) {
const action = queue[0];
try {
await fetch(action.url, action);
queue.shift();
localStorage.setItem('queue', JSON.stringify(queue));
} catch {
break; // stop until online
}
}
}
window.addEventListener('online', trySend);Diffing Incoming Real-time Data
If the server pushes the entire list, diff against current state and update only the changes — avoids unnecessary re-renders and animation glitches.
function applyServerSnapshot(newList, currentList) {
const current = new Map(currentList.map(i => [i.id, i]));
const result = newList.map(item => {
const prev = current.get(item.id);
if (!prev) return { ...item, _state: 'new' };
if (prev.updatedAt !== item.updatedAt) return { ...item, _state: 'updated' };
return prev;
});
return result;
}Patch-Based Updates
Better than full snapshots: server sends just the changed fields. Apply incrementally — uses less bandwidth and is faster to render.
// Server sends: { type: 'patch', id: 42, fields: { likes: 11 } }
ws.on('message', (msg) => {
if (msg.type === 'patch') {
setPosts(posts => posts.map(p =>
p.id === msg.id ? { ...p, ...msg.fields } : p
));
}
});Throttling High-Frequency Updates
Some streams send many updates per second (mouse position, stock ticks). Throttle UI updates to ~60Hz to avoid janking the browser.
import { throttle } from 'lodash-es';
const applyUpdate = throttle((data) => {
setData(data);
}, 16); // ~60fps
ws.on('tick', applyUpdate);Presence Indicators
Show who else is online/typing in real-time. Server publishes presence events; client renders a list with avatars.
ws.on('presence', ({ users }) => {
setOnlineUsers(users);
});
ws.on('typing', ({ user, isTyping }) => {
setTypingUsers(t => isTyping
? [...t.filter(u => u.id !== user.id), user]
: t.filter(u => u.id !== user.id));
});Connection State Banner
Show a subtle banner when offline or reconnecting. Hide it on full reconnect. Users tolerate latency much better when they can see what's happening.
{!isConnected && (
<div className="connection-banner">
Reconnecting... Your changes will sync when you're back online.
</div>
)}Conflict Resolution
Real-time apps must handle conflicting concurrent edits. Strategies: last-write-wins (simple but lossy), version vectors, OT (operational transformation), CRDTs (Yjs, Automerge).
CRDTs for Collaboration
CRDTs (Conflict-free Replicated Data Types) enable real-time collaborative editing without a central server. Libraries: Yjs, Automerge. Used by Figma, Linear, Notion-style apps.
Performance: Virtualised Lists
Real-time feeds can grow long. Use virtualised lists (react-virtuoso, @tanstack/react-virtual) to render only the visible items — fixed memory and CPU regardless of total count.
Quick Check
What is an 'optimistic update' in a real-time UI?
Recap: Real-time UI Patterns
Optimistic updates make apps feel instant; rollback on failure. Temporary IDs for created items. Local queuing for offline durability. Diff or patch server updates to avoid re-renders. Throttle high-frequency streams. Show presence (online, typing) and connection banners. CRDTs (Yjs, Automerge) for collaborative editing. Virtualise long real-time lists.
Frequently asked questions
Is the “Real-time UI Patterns” lesson free?
Yes — the full text of “Real-time UI Patterns” is free to read here on the web, and the Frontend Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Frontend Academy course, upgrade to CoddyKit PRO.
What will I learn in “Real-time UI Patterns”?
Apply optimistic updates for instant feedback, use local queuing when offline, and diff incoming data to update only changed items in the UI. You practise Frontend Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Frontend Academy?
No prior experience is required. Frontend Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Real-time UI Patterns” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Frontend Academy lesson?
Yes. Every Frontend Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.