Choosing a framework is easy; architecting a long-lived application within it is the real challenge for mid-level engineers.
1The Component Philosophy
Components should be 'Smart' (handling logic and data) or 'Dumb' (handling only UI). This 'Atomic' separation is what allows a codebase to remain maintainable as it grows to thousands of files.
2Predictable Data Flow
Whether you use Redux, Pinia, or RxJS, the goal is the same: Unidirectional Data Flow. Data flows down, actions flow up. This pattern eliminates the 'zombie state' bugs that plague junior projects.
3Optimization Hygiene
Performance isn't an afterthought. Mid-level developers use Profilers to find 'Wasted Renders' and understand how to use 'Lazy Loading' to keep initial bundle sizes small and fast.
4Step-by-Step Breakdown
A junior knows how to use a framework. A mid-level knows why to use it. We're looking at the architectural patterns that make modern applications scalable.
Understanding the lifecycle—when a component mounts, updates, or unmounts—is critical for managing side effects, memory leaks, and performance bottlenecks.
Global state isn't just about 'Redux'. It's about knowing when to use Local State, Context/Services, or External Stores to keep data flow predictable.
Virtual DOM vs. Signals vs. Direct DOM. Understanding how your framework actually updates the screen is the key to optimizing complex interfaces.
What is the primary benefit of 'Dependency Injection' in frameworks like Angular?
- →It makes the application run faster in the browser
- →It allows for better decoupling and easier testing of services
- →It automatically minifies your JavaScript files
- →It prevents users from seeing your source code
In React, why should you use the 'useMemo' or 'useCallback' hooks?
- →To make the code look more modern
- →To prevent unnecessary re-renders of child components and expensive calculations
- →To fetch data from a backend API
- →To bypass the Virtual DOM entirely
Level Up 🚀
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Focus Management Across Client-Side Routes
When a framework router swaps views without a full page reload, the browser doesn't automatically move focus or announce the change like it does on a hard navigation. Move focus to the new view's heading and use an aria-live region so screen reader users know the route changed.
// On route change
headingRef.current?.focus();
liveRegionRef.current.textContent = `Navigated to ${pageTitle}`;SEO Implications
- 1
Hydration Cost and Crawl Budget
Client-rendered frameworks that ship a large JS bundle before content becomes visible delay both user interaction and crawler rendering. Server-side rendering or static generation for content-heavy routes keeps meaningful HTML available before hydration finishes.
Best Practices
Keep Derived State Out of the Store
Values that can be computed from existing state (a filtered list, a total) shouldn't be duplicated into the store or component state. Compute them with a selector or memoized function so they can never drift out of sync with their source.
Clean Up Side Effects on Unmount
Subscriptions, timers, and event listeners started in a lifecycle hook or effect must be torn down when the component unmounts, or they keep running against a component that no longer exists and leak memory.
Frequent Bugs
An effect or lifecycle hook fires an API call on every render because an object or array literal is recreated each time and passed as a dependency, so the dependency 'changes' even though its contents didn't.
Memoize the object/array with useMemo (or the framework's equivalent) so its reference stays stable across renders, or depend on primitive values extracted from it instead of the whole object.
Real-World Examples
Preventing Wasted Re-renders in a List
A dashboard renders a list of a few hundred rows, and typing in an unrelated filter input causes every row component to re-render even though their data hasn't changed.
const Row = React.memo(function Row({ item }) {
return <li>{item.label}</li>;
});
// Parent passes a stable `item` reference per row so memo can skip re-renders