π Senior Frontend Engineer / React Developer Interview Handbook
Part 2: Sections 6β10 | State Management β Router β API β Patterns β System Design
SECTION 6: State Management
6.1 When to Use External State Management?
Decision Framework
Local UI State (show/hide modal, form inputs)
β useState / useReducer
Shared State (a few components)
β Context API + useReducer
Complex shared state, many updates, DevTools needed
β Redux Toolkit / Zustand
Server State (cache, async data)
β React Query / TanStack Query / SWR
Derived/Atomic state, fine-grained subscriptions
β Recoil / Jotai
6.2 Context API
Concept
Context provides a way to pass data through the component tree without prop drilling. It is built into React β no extra library needed.
Architecture
React.createContext(defaultValue)
β
βΌ
<Context.Provider value={...}> β Provides value to subtree
β
ββ <ConsumerA /> β useContext(Context) β subscribes
ββ <ConsumerB /> β useContext(Context) β subscribes
ββ ...
Complete Pattern
// 1. Create context + custom hook
const UserContext = createContext(null);
function useUser() {
const ctx = useContext(UserContext);
if (!ctx) throw new Error("useUser must be used within UserProvider");
return ctx;
}
// 2. Provider with state
function UserProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const login = useCallback(async (credentials) => {
setLoading(true);
try {
const userData = await api.login(credentials);
setUser(userData);
} finally {
setLoading(false);
}
}, []);
const logout = useCallback(() => {
setUser(null);
}, []);
const value = useMemo(
() => ({ user, loading, login, logout }),
[user, loading, login, logout]
);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// 3. Usage anywhere in tree
function NavBar() {
const { user, logout } = useUser();
return (
<header>
{user ? <button onClick={logout}>Logout</button> : <LoginLink />}
</header>
);
}
Performance: Context Splitting
// β Single context: ALL consumers re-render when ANYTHING changes
const AppContext = createContext({
user: null,
theme: "light",
sidebarOpen: false,
});
// β
Split by change frequency
const UserContext = createContext(null); // rarely changes
const ThemeContext = createContext("light"); // changes on toggle
const UIContext = createContext({}); // changes frequently (sidebar, modal)
Context vs Redux
| Β | Context API | Redux |
|---|---|---|
| Bundle size | 0KB (built-in) | ~10KB (RTK) |
| DevTools | Basic React DevTools | Powerful Redux DevTools |
| Middleware | No | Yes (thunk, saga) |
| Performance | Re-renders all consumers | Selective subscriptions |
| Boilerplate | Low | Medium (RTK reduced it) |
| Best for | Simple/medium apps | Large apps with complex state |
6.3 Redux & Redux Toolkit (RTK)
π Deep dive: Redux & RTK Complete Guide β Plain Redux from scratch β RTK Slices β RTK Query β Testing β Interview Q&A
Architecture
View (React Component)
β dispatch(action)
βΌ
Store β Reducer (pure function: (state, action) β newState)
β state
βΌ
View (re-renders via useSelector)
Redux Toolkit β Modern Redux
// store/userSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
// Async thunk for API calls
export const fetchUser = createAsyncThunk("user/fetch", async (userId) => {
const response = await api.getUser(userId);
return response.data;
});
const userSlice = createSlice({
name: "user",
initialState: { data: null, loading: false, error: null },
reducers: {
logout: (state) => {
state.data = null; // Immer allows this "mutation" syntax!
},
updateName: (state, action) => {
state.data.name = action.payload;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.loading = true;
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(fetchUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export const { logout, updateName } = userSlice.actions;
export default userSlice.reducer;
// store/index.js
import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
export const store = configureStore({
reducer: {
user: userReducer,
// add more slices
},
});
// In component
function UserProfile() {
const dispatch = useDispatch();
const { data: user, loading } = useSelector((state) => state.user);
useEffect(() => {
dispatch(fetchUser("123"));
}, []);
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
}
Why Redux Uses Immutability
- Change detection: React-Redux uses
===to detect state changes for selector optimization. Mutation breaks this. - Time-travel debugging: Immutable history allows Redux DevTools to replay state changes.
- Predictability: Pure reducers are easy to test and reason about.
RTK uses Immer internally β you write mutations but Immer produces a new immutable object behind the scenes.
RTK Query β Server State in Redux
// api/postsApi.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const postsApi = createApi({
reducerPath: "postsApi",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
tagTypes: ["Post"],
endpoints: (builder) => ({
getPosts: builder.query({ query: () => "/posts", providesTags: ["Post"] }),
addPost: builder.mutation({
query: (body) => ({ url: "/posts", method: "POST", body }),
invalidatesTags: ["Post"], // auto-refetch getPosts after add
}),
}),
});
export const { useGetPostsQuery, useAddPostMutation } = postsApi;
// Component β no manual loading/error state!
function Posts() {
const { data: posts, isLoading, error } = useGetPostsQuery();
const [addPost] = useAddPostMutation();
if (isLoading) return <Spinner />;
if (error) return <Error />;
return <PostList posts={posts} onAdd={addPost} />;
}
6.4 Zustand
Concept
Zustand is a minimal, fast state management library based on hooks. No boilerplate, no providers.
import { create } from "zustand";
// Create store
const useStore = create((set, get) => ({
// State
user: null,
cart: [],
// Actions
setUser: (user) => set({ user }),
addToCart: (item) =>
set((state) => ({
cart: [...state.cart, item],
})),
removeFromCart: (id) =>
set((state) => ({
cart: state.cart.filter((i) => i.id !== id),
})),
get cartTotal() {
return get().cart.reduce((sum, item) => sum + item.price, 0);
},
}));
// Usage β no Provider needed!
function Cart() {
const { cart, removeFromCart, cartTotal } = useStore();
return (
<div>
Total: ${cartTotal}
{cart.map((item) => (
<div key={item.id}>
{item.name}
<button onClick={() => removeFromCart(item.id)}>Remove</button>
</div>
))}
</div>
);
}
// Subscribe to only what you need β no unnecessary re-renders
function UserName() {
const userName = useStore((state) => state.user?.name); // only re-renders when name changes
return <span>{userName}</span>;
}
Zustand vs Redux
| Β | Zustand | Redux Toolkit |
|---|---|---|
| Setup | Minimal | Medium |
| Bundle size | ~1KB | ~10KB |
| Provider | Not needed | Needed |
| DevTools | Plugin | Built-in |
| Middleware | Supports | Rich ecosystem |
| TypeScript | Excellent | Excellent |
| Best for | Small-medium apps | Large apps |
6.5 Jotai & Recoil (Atomic State)
Concept
Atomic state management: state is split into tiny atoms. Components subscribe to only the atoms they use.
// Jotai
import { atom, useAtom } from "jotai";
const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2); // derived atom
function Counter() {
const [count, setCount] = useAtom(countAtom);
const doubled = useAtomValue(doubledAtom);
return (
<div>
{count} Γ 2 = {doubled}{" "}
<button onClick={() => setCount((c) => c + 1)}>+</button>
</div>
);
}
Benefits: Fine-grained subscriptions β component only re-renders when its specific atoms change. Great for large apps with many independent state slices.
SECTION 7: React Router
7.1 React Router DOM v6
Basic Setup
import {
BrowserRouter,
Routes,
Route,
Link,
useNavigate,
useParams,
} from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserProfile />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
function UserProfile() {
const { id } = useParams();
const navigate = useNavigate();
return (
<div>
User {id} <button onClick={() => navigate(-1)}>Back</button>
</div>
);
}
Nested Routes
// Parent layout with <Outlet />
function Dashboard() {
return (
<div className="dashboard">
<Sidebar />
<main>
<Outlet /> {/* Child routes render here */}
</main>
</div>
);
}
<Routes>
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} /> {/* /dashboard */}
<Route path="analytics" element={<Analytics />} /> {/* /dashboard/analytics */}
<Route path="settings" element={<Settings />} /> {/* /dashboard/settings */}
</Route>
</Routes>;
Protected Routes
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
// Redirect to login, remember intended destination
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// Usage
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>;
// In login: redirect back
function Login() {
const location = useLocation();
const navigate = useNavigate();
function handleLogin() {
// ...authenticate...
const from = location.state?.from?.pathname || "/";
navigate(from, { replace: true });
}
}
Lazy Loaded Routes
const Dashboard = React.lazy(() => import("./pages/Dashboard"));
const Profile = React.lazy(() => import("./pages/Profile"));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
BrowserRouter vs HashRouter
| Β | BrowserRouter | HashRouter |
|---|---|---|
| URL format | /dashboard/analytics |
/#/dashboard/analytics |
| Server config | Needs server to serve index.html for all routes |
No server config needed |
| SEO | Better | Worse (hash part ignored by crawlers) |
| Use case | Modern web apps | Static file hosting, legacy |
| History API | pushState | hash change |
SECTION 8: API Handling
8.1 Fetch API & Axios
Fetch API
// Basic fetch
const response = await fetch("/api/users");
if (!response.ok) throw new Error(`HTTP error: ${response.status}`);
const data = await response.json();
// With options
const response = await fetch("/api/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(payload),
signal: abortController.signal,
});
Axios
import axios from "axios";
// Create instance with defaults
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL,
timeout: 10000,
headers: { "Content-Type": "application/json" },
});
// Request interceptor β add auth token
api.interceptors.request.use(
(config) => {
const token = getToken();
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor β handle 401, refresh token
api.interceptors.response.use(
(response) => response,
async (error) => {
const original = error.config;
if (error.response?.status === 401 && !original._retry) {
original._retry = true;
const newToken = await refreshToken();
original.headers.Authorization = `Bearer ${newToken}`;
return api(original); // retry with new token
}
return Promise.reject(error);
}
);
Axios vs Fetch
| Β | Fetch | Axios |
|---|---|---|
| Built-in | Yes (browser/Node 18+) | No (3rd party) |
| Auto JSON parse | No (manual .json()) |
Yes |
| Interceptors | No | Yes |
| Timeout | Manual (AbortController) | Built-in |
| Progress | No | Yes |
| Error for 4xx/5xx | No (check response.ok) |
Yes (throws) |
| Bundle size | 0KB | ~13KB |
8.2 React Query / TanStack Query
Why React Query?
Without React Query (manual data fetching):
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch("/api/users")
.then((r) => r.json())
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((err) => {
setError(err);
setLoading(false);
});
}, []);
// No caching β refetches on every mount
// No background refetch
// No deduplication
// Must manage loading/error manually
}
With React Query:
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
function Users() {
// Cached, deduped, background-refetched automatically
const {
data: users,
isLoading,
error,
} = useQuery({
queryKey: ["users"],
queryFn: () => fetch("/api/users").then((r) => r.json()),
staleTime: 5 * 60 * 1000, // consider fresh for 5 minutes
});
if (isLoading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <UserList users={users} />;
}
// Mutations with optimistic updates
function useUpdateUser() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ id, data }) => api.put(`/users/${id}`, data),
onMutate: async ({ id, data }) => {
// Cancel in-flight queries
await queryClient.cancelQueries({ queryKey: ["users"] });
// Snapshot current data
const previous = queryClient.getQueryData(["users"]);
// Optimistically update
queryClient.setQueryData(["users"], (old) =>
old.map((u) => (u.id === id ? { ...u, ...data } : u))
);
return { previous }; // context for rollback
},
onError: (err, vars, context) => {
// Rollback on error
queryClient.setQueryData(["users"], context.previous);
},
onSettled: () => {
// Always refetch after mutation
queryClient.invalidateQueries({ queryKey: ["users"] });
},
});
}
Client State vs Server State
| Β | Client State | Server State |
|---|---|---|
| Location | Browser memory | Remote database |
| Examples | Theme, modal open, form | Users, products, posts |
| Ownership | Frontend app | Backend / API |
| Library | useState, Zustand | React Query, SWR |
| Stale? | Always current | Can be stale |
| Sync issues | No | Yes (cache invalidation) |
React Query Features
- Caching: Responses cached by query key.
- Background refetch: Refetch on window focus, reconnect.
- Deduplication: Multiple components with same query = 1 request.
- Pagination/Infinite:
useInfiniteQuerybuilt-in. - Stale-while-revalidate: Show stale data while fetching fresh.
- Retry on failure: Auto-retry with exponential backoff.
8.3 SWR
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
function Profile() {
const { data, error, isLoading } = useSWR("/api/user", fetcher, {
revalidateOnFocus: true,
refreshInterval: 30000, // polling every 30s
});
if (isLoading) return <Spinner />;
if (error) return <Error />;
return <div>{data.name}</div>;
}
SWR vs React Query: React Query is more feature-rich (devtools, mutations, optimistic updates). SWR is simpler and smaller. Both use stale-while-revalidate pattern.
SECTION 9: Advanced React Patterns
9.1 Higher-Order Components (HOC)
Concept
A HOC is a function that takes a component and returns a new enhanced component.
// Pattern: withAuth HOC
function withAuth(WrappedComponent) {
return function AuthenticatedComponent(props) {
const { user } = useAuth();
if (!user) return <Redirect to="/login" />;
return <WrappedComponent {...props} user={user} />;
};
}
// Usage
const ProtectedDashboard = withAuth(Dashboard);
// Pattern: withLogging HOC
function withLogging(WrappedComponent, componentName) {
return function LoggedComponent(props) {
useEffect(() => {
console.log(`[${componentName}] mounted`);
return () => console.log(`[${componentName}] unmounted`);
}, []);
return <WrappedComponent {...props} />;
};
}
Drawbacks of HOCs
- Wrapper hell: Deeply nested
withA(withB(withC(Component))). - Props collision: HOC might pass a prop with the same name as an existing prop.
- Opaque in DevTools: Shows as
withAuth(Dashboard)notDashboard. - Static typing is harder: Complex TypeScript generics needed.
Modern React: Prefer Custom Hooks over HOCs for logic reuse.
9.2 Render Props
Concept
A component accepts a function as a prop (render or children) and calls it to render its output. The function receives data.
// Mouse tracker using render prop
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={e => setPosition({ x: e.clientX, y: e.clientY })}>
{children(position)} {/* Call the children function with data */}
</div>
);
}
// Usage
<MouseTracker>
{({ x, y }) => <div>Mouse: {x}, {y}</div>}
</MouseTracker>
// More commonly: named render prop
<DataProvider
render={({ data, loading }) =>
loading ? <Spinner /> : <List data={data} />
}
/>
Modern React: Prefer Custom Hooks over Render Props. The above becomes
useMouse().
9.3 Compound Components
Concept
A pattern where a parent component shares implicit state with its children via Context. Children are semantically related but independently placed.
// Example: Compound Select component
const SelectContext = createContext();
function Select({ children, value, onChange }) {
return (
<SelectContext.Provider value={{ value, onChange }}>
<div className="select">{children}</div>
</SelectContext.Provider>
);
}
function Option({ value, children }) {
const { value: selected, onChange } = useContext(SelectContext);
return (
<div
className={`option ${selected === value ? "selected" : ""}`}
onClick={() => onChange(value)}>
{children}
</div>
);
}
// Attach as static properties for convenience
Select.Option = Option;
// Usage β flexible composition
<Select value={selected} onChange={setSelected}>
<Select.Option value="react">React</Select.Option>
<Select.Option value="vue">Vue</Select.Option>
<Select.Option value="angular">Angular</Select.Option>
</Select>;
Real-World Examples
<Tabs>,<Tab>,<TabPanel>(Radix UI, Headless UI)<Menu>,<MenuItem>(Reach UI)<Accordion>,<AccordionItem>patterns
9.4 Provider Pattern
Concept
Wrapping a component tree with a provider that supplies dependencies/services (theme, i18n, auth) via Context.
// Pattern: Multiple providers composed
function AppProviders({ children }) {
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
<AuthProvider>
<RouterProvider>{children}</RouterProvider>
</AuthProvider>
</ThemeProvider>
</QueryClientProvider>
);
}
function App() {
return (
<AppProviders>
<Routes />
</AppProviders>
);
}
9.5 Headless Components
Concept
Components that provide behavior and accessibility but no styling. Consumers bring their own UI.
// Headless Accordion (no styles, just logic + a11y)
function useAccordion(items) {
const [openIndex, setOpenIndex] = useState(null);
const getItemProps = (index) => ({
isOpen: openIndex === index,
toggle: () => setOpenIndex((i) => (i === index ? null : index)),
});
return { getItemProps };
}
// Libraries: Radix UI, Headless UI (Tailwind), Downshift, React Aria
import * as Accordion from "@radix-ui/react-accordion";
// Users apply their own styling
<Accordion.Root type="single">
<Accordion.Item value="item-1">
<Accordion.Trigger className="my-custom-trigger">
Question
</Accordion.Trigger>
<Accordion.Content className="my-custom-content">Answer</Accordion.Content>
</Accordion.Item>
</Accordion.Root>;
9.6 Controlled vs Uncontrolled Component Pattern
Concept for Libraries (not forms)
Component libraries expose both controlled and uncontrolled modes:
// Uncontrolled: component manages its own state
<Accordion defaultOpen={['item-1']} />
// Controlled: parent manages state
<Accordion open={openItems} onOpenChange={setOpenItems} />
// Implementation: useControllableState pattern
function useControllableState({ prop, defaultProp, onChange }) {
const [uncontrolled, setUncontrolled] = useState(defaultProp);
const isControlled = prop !== undefined;
const value = isControlled ? prop : uncontrolled;
const setValue = useCallback((next) => {
if (!isControlled) setUncontrolled(next);
onChange?.(next);
}, [isControlled, onChange]);
return [value, setValue];
}
SECTION 10: React System Design
10.1 Frontend System Design Framework
For every system design question, follow this structure:
1. Clarify Requirements
β Functional: What features?
β Non-functional: Scale? Performance? Offline? Accessibility?
2. Component Architecture
β Break UI into components
β Identify shared/reusable parts
3. State Management
β What state exists?
β Where does it live?
β Client vs Server state?
4. Data Flow & API Layer
β API contracts
β Caching strategy
β Real-time needs (WebSocket, SSE, Polling)?
5. Routing Structure
β URL design
β Code splitting strategy
6. Performance
β Initial load
β Runtime performance
β Bundle optimization
7. Error Handling & Edge Cases
8. Accessibility
9. Security
10. Monitoring & Observability
10.2 Design: Google Docs Frontend
Requirements
- Real-time collaborative editing
- Rich text formatting
- User presence (cursors)
- Offline support
- Version history
Architecture
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β React App β
β ββββββββββββββ ββββββββββββββββ βββββββββββββββββββ β
β β Toolbar β β Document β β Collaborators β β
β β Component β β Editor β β Panel β β
β ββββββββββββββ ββββββββ¬ββββββββ ββββββββββ¬βββββββββ β
β β β β
β ββββββββββββββββββββββββΌββββββββββββββββββββΌβββββββββ β
β β Editor State (Slate.js / ProseMirror) β β
β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββ β
β β Collaboration Layer (Yjs / CRDT) β β
β ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ
β WebSocket
βββββββΌβββββββ
β Backend β
β (CRDT β
β Sync) β
ββββββββββββββ
Key Technical Decisions
Real-time sync: Use CRDT (Conflict-free Replicated Data Types) via Yjs. CRDTs allow offline editing and automatic merge without conflicts.
State management:
- Editor content: Yjs document (distributed state)
- UI state: Zustand (sidebar open, active toolbar, user presence)
- User data: React Query (profile, permissions)
Performance:
- Virtual rendering for large documents
- Only re-render affected paragraphs on edit
- Debounce network sync (donβt send every keystroke)
Offline support:
- IndexedDB persistence via
y-indexeddb - Service Worker for asset caching
- Sync when reconnected
10.3 Design: Instagram Feed
Requirements
- Infinite scroll feed
- Like/comment/share
- Stories
- Image optimization
- Notifications
Component Architecture
App
βββ Stories (horizontal scroll)
β βββ StoryItem
βββ Feed (infinite scroll)
β βββ Post
β βββ PostHeader (user info)
β βββ PostMedia (image/video)
β βββ PostActions (like, comment, share)
β βββ PostComments (collapsed)
βββ BottomNav
State Management
Server State (React Query):
- /feed?cursor=xxx β feed posts
- /stories β stories
- /users/:id β user profiles
Client State (Zustand):
- likedPosts: Set<postId> (optimistic updates)
- savedPosts: Set<postId>
- activeModal: null | 'comments' | 'share'
Real-time (WebSocket):
- New posts from followed users
- Like/comment counts updates
- DM notifications
Infinite Scroll Implementation
function Feed() {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } =
useInfiniteQuery({
queryKey: ['feed'],
queryFn: ({ pageParam = null }) =>
api.getFeed({ cursor: pageParam }),
getNextPageParam: (lastPage) => lastPage.nextCursor ?? undefined,
});
const posts = data?.pages.flatMap(page => page.posts) ?? [];
// Intersection Observer for infinite scroll
const loadMoreRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => { if (entry.isIntersecting && hasNextPage) fetchNextPage(); },
{ threshold: 0.1 }
);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
return () => observer.disconnect();
}, [hasNextPage, fetchNextPage]);
return (
<VirtualList items={posts} renderItem={(post) => <Post key={post.id} post={post} />} />
<div ref={loadMoreRef}>{isFetchingNextPage && <Spinner />}</div>
);
}
Performance
- Image lazy loading:
loading="lazy"+ Intersection Observer - Image optimization: WebP format, CDN, srcset for responsive
- Virtualization: react-window for large feeds
- Prefetch: Load next page before user reaches bottom
10.4 Design: Real-Time Chat Application
Architecture
Frontend:
βββ ConversationList (sidebar)
βββ MessageThread (main area)
β βββ MessageList (virtualized)
β βββ MessageInput (controlled, emoji picker)
βββ OnlinePresence
Transport: WebSocket (Socket.io or native)
Fallback: Long Polling
State:
βββ Server: React Query (conversations, messages)
βββ Real-time: Socket.io events β React Query cache updates
βββ Client: Zustand (active conversation, typing indicators, online status)
WebSocket Integration
// Real-time chat with React Query integration
function useChatSocket(userId) {
const queryClient = useQueryClient();
useEffect(() => {
const socket = io("/chat", { auth: { token: getToken() } });
socket.on("message:new", (message) => {
// Update React Query cache directly β no refetch needed
queryClient.setQueryData(["messages", message.conversationId], (old) =>
old ? [...old, message] : [message]
);
// Update conversation preview
queryClient.invalidateQueries({ queryKey: ["conversations"] });
});
socket.on("typing:start", ({ userId, conversationId }) => {
useTypingStore.setState((s) => ({
typing: {
...s.typing,
[conversationId]: [...(s.typing[conversationId] ?? []), userId],
},
}));
});
return () => socket.disconnect();
}, [userId]);
}
10.5 Design: E-Commerce Frontend
Folder Structure
src/
βββ app/ # App-level config
β βββ store.ts # Redux/Zustand store
β βββ queryClient.ts # React Query client
β βββ router.tsx # Route definitions
β
βββ features/ # Feature-sliced design
β βββ catalog/
β β βββ components/ # ProductCard, FilterBar, SortSelect
β β βββ hooks/ # useProducts, useFilters
β β βββ api/ # productsApi.ts
β β βββ store/ # catalogSlice.ts
β β
β βββ cart/
β β βββ components/ # CartDrawer, CartItem, CartSummary
β β βββ hooks/ # useCart
β β βββ store/ # cartSlice.ts (persisted)
β β
β βββ checkout/
β β βββ components/ # ShippingForm, PaymentForm, OrderReview
β β βββ hooks/ # useCheckout
β β βββ api/ # checkoutApi.ts
β β
β βββ auth/
β βββ components/ # LoginForm, RegisterForm
β βββ hooks/ # useAuth
β βββ api/ # authApi.ts
β
βββ shared/ # Cross-cutting concerns
β βββ ui/ # Design system components
β βββ hooks/ # useDebounce, useLocalStorage
β βββ utils/ # formatPrice, validateEmail
β βββ types/ # TypeScript types
β
βββ pages/ # Route-level components
βββ HomePage.tsx
βββ CatalogPage.tsx
βββ ProductPage.tsx
βββ CartPage.tsx
βββ CheckoutPage.tsx
Critical E-Commerce Performance
// 1. Product images: CDN + WebP + blur placeholder
<img
src={product.imageUrl}
loading="lazy"
decoding="async"
style={{ backgroundImage: `url(${product.blurDataURL})` }}
/>;
// 2. Search: Debounced + React Query
const debouncedSearch = useDebounce(searchTerm, 300);
const { data } = useQuery({
queryKey: ["products", debouncedSearch, filters],
queryFn: () => searchProducts(debouncedSearch, filters),
keepPreviousData: true, // no loading flash on filter change
});
// 3. Cart: Persistent + Optimistic
// Persist in localStorage, sync to server
// Optimistic: update cart UI before server confirms
// 4. Product page: Code split heavy components
const ReviewSection = lazy(() => import("./ReviewSection"));
const RelatedProducts = lazy(() => import("./RelatedProducts"));
// Load above-fold first, defer below-fold
10.6 Design: Analytics Dashboard
Architecture
Dashboard
βββ DateRangePicker
βββ FilterPanel
βββ MetricsGrid
β βββ MetricCard (Revenue, Users, Conversions)
β βββ TrendChart
βββ ChartSection
β βββ LineChart (time series)
β βββ BarChart (comparison)
β βββ PieChart (distribution)
βββ DataTable (top pages, top products)
State Design
// URL as state for shareable dashboards
// /analytics?from=2024-01-01&to=2024-01-31&segments=organic,paid
function useDashboardFilters() {
const [searchParams, setSearchParams] = useSearchParams();
const filters = useMemo(
() => ({
dateRange: {
from: searchParams.get("from") ?? getDefaultFrom(),
to: searchParams.get("to") ?? getDefaultTo(),
},
segments: searchParams.getAll("segments"),
}),
[searchParams]
);
const setFilters = useCallback((newFilters) => {
setSearchParams(toSearchParams(newFilters), { replace: true });
}, []);
return { filters, setFilters };
}
// React Query with refetch interval for live dashboards
const { data } = useQuery({
queryKey: ["metrics", filters],
queryFn: () => fetchMetrics(filters),
refetchInterval: 60_000, // refresh every minute
staleTime: 30_000,
});
End of Part 2 β Sections 6β10