I used to think React was just "smart about re-renders." Then I actually dug into how reconciliation works. And I realized I had been writing components that quietly fought the algorithm for years. Here's what's actually happening under the hood — and why it matters for your app's performance. — When state changes, React doesn't touch the real DOM immediately. It builds a new Virtual DOM tree, compares it to the previous one, and figures out the minimum set of changes needed. That comparison process is reconciliation. The naive version of this problem is O(n³). For 1000 elements — that's a billion comparisons. Completely unusable. So React cheats. In a smart way. It uses a heuristic O(n) algorithm built on two assumptions: → Elements of different types always produce different trees → Keys tell React which elements are stable across renders Simple rules. Massive performance gain. — Here's where it gets interesting — and where most bugs actually come from. Rule 1 in practice: If you swap a <div> for a <section>, React tears down the entire subtree and rebuilds from scratch. Every child component unmounts. All local state is lost. This isn't a bug. It's the algorithm doing exactly what you told it to do. I've seen this cause subtle bugs in forms — a wrapper element change during a conditional render, and suddenly input state resets mid-user interaction. — Rule 2 in practice — the key trap: Using array index as a key is one of the most common mistakes I've reviewed in code. If you have a list and insert an item in the middle, every index below it shifts. React sees completely new keys, throws away the existing nodes, and rebuilds them. What looked like a simple insert becomes a full list re-render. Use stable, unique IDs. Always. — Fiber changed everything in React 16. Before Fiber, reconciliation was synchronous — once started, it couldn't be interrupted. A heavy render could block the main thread and freeze the UI. Fiber broke rendering into small units of work. React can now pause, prioritize, and resume rendering. That's what powers Concurrent Mode, Suspense, and transitions. The algorithm didn't change. The scheduler around it did. — Practical things I now do differently because of this: → Never create component definitions inside render — new reference = React thinks it's a different type = full unmount every render → Keys on lists always come from data, never from index → Wrap stable subtrees in React.memo when the parent re-renders frequently → Use the Profiler in DevTools to actually see which reconciliation decisions are expensive — Reconciliation is one of those things that's easy to ignore until performance starts hurting. But once you understand the two rules React operates on, a lot of "React behaves weirdly" moments suddenly make complete sense. What's the most unexpected reconciliation bug you've run into? #react #frontend #javascript #webdev #reactjs #frontenddevelopment #softwaredevelopment
Artem Krasovskyi’s Post
More Relevant Posts
-
A React component re-renders when state or props change. It also re-renders when its parent re-renders. That second one is where most performance bugs hide. On a trading dashboard updating 10 times per second, every wasted render is a dropped frame. Users notice. The app feels sluggish. And the fix isn't "rewrite it" — it's understanding three hooks and when to actually reach for them. The memoization toolkit in React is useMemo, useCallback, and React.memo. Most devs know what they do. Fewer know when not to use them. USEMEMO — CACHE AN EXPENSIVE COMPUTATION const processedData = useMemo(() => rawTrades.filter(t => t.volume > 1000).sort(...), [rawTrades] // only recomputes when rawTrades changes ) Use this when the derivation is genuinely expensive — sorting, filtering, or aggregating large datasets. Don't use it to memoize a string concatenation. The cache itself has a cost. USECALLBACK — STABLE FUNCTION REFERENCE FOR CHILD PROPS const handleSelect = useCallback((id: string) => { setSelected(id) }, []) // same reference across renders = child won't re-render Without this, a new function is created every render. If that function is passed as a prop to a memoized child, it breaks the memo — the child sees a "changed" prop and re-renders anyway. REACT.MEMO — SKIP RENDER IF PROPS HAVEN'T CHANGED const TradeRow = memo(({ trade }: { trade: Trade }) => { return <tr>{trade.symbol}</tr> }) Only works when the props are stable. If you're passing a new object or function reference on every render, memo does nothing — you need useMemo and useCallback upstream first. Here's the answer pattern that lands well in interviews: "I'd profile first with React DevTools to confirm which components are actually re-rendering unnecessarily. Then I'd stabilise callback references with useCallback, memoize expensive derivations with useMemo, and wrap pure display components with React.memo. Memoization is a last resort — not a default — because it adds overhead. The profiler tells you where that overhead is justified." The word "profile" is doing a lot of work in that answer. It signals that you don't guess at performance problems — you measure them. That's what separates the senior answer from the junior one. The trap interviewers set: "So should I just wrap everything in memo?" The correct answer is no — and knowing why not is the whole point. What's the worst unnecessary re-render bug you've debugged? Drop it below 👇 #React #JavaScript #FrontendDevelopment #WebPerformance #SoftwareEngineering
To view or add a comment, sign in
-
-
⚛️ React feels “fast”… but why? It’s not magic. It’s the Diffing Algorithm (Reconciliation) working behind the scenes. Let’s break it down so you actually understand what React is doing internally 👇 🧠 𝗧𝗵𝗲 𝗖𝗼𝗿𝗲 𝗜𝗱𝗲𝗮 Every time state/props change, React: 1. Creates a new Virtual DOM 2. Compares it with the previous Virtual DOM 3. Updates ONLY the changed parts in the real DOM 👉 This comparison = Diffing ⚙️ 𝗕𝘂𝘁 𝗵𝗼𝘄 𝗱𝗼𝗲𝘀 𝗥𝗲𝗮𝗰𝘁 𝗱𝗶𝗳𝗳 𝗲𝗳𝗳𝗶𝗰𝗶𝗲𝗻𝘁𝗹𝘆? React doesn’t compare everything blindly (that would be slow ❌) Instead, it follows 2 smart assumptions: 1️⃣ 𝗗𝗶𝗳𝗳𝗲𝗿𝗲𝗻𝘁 𝘁𝘆𝗽𝗲 = 𝗥𝗲𝗽𝗹𝗮𝗰𝗲 𝗲𝘃𝗲𝗿𝘆𝘁𝗵𝗶𝗻𝗴 <𝘥𝘪𝘷>𝘏𝘦𝘭𝘭𝘰</𝘥𝘪𝘷> ➡️ becomes <𝘴𝘱𝘢𝘯>𝘏𝘦𝘭𝘭𝘰</𝘴𝘱𝘢𝘯> React says: “Type changed? Cool, destroy old node, create new one” No deep comparison. 2️⃣ 𝗦𝗮𝗺𝗲 𝘁𝘆𝗽𝗲 = 𝗖𝗼𝗺𝗽𝗮𝗿𝗲 𝗽𝗿𝗼𝗽𝘀 <𝘥𝘪𝘷 𝘤𝘭𝘢𝘴𝘴="𝘳𝘦𝘥">𝘏𝘦𝘭𝘭𝘰</𝘥𝘪𝘷> ➡️ becomes <𝘥𝘪𝘷 𝘤𝘭𝘢𝘴𝘴="𝘣𝘭𝘶𝘦">𝘏𝘦𝘭𝘭𝘰</𝘥𝘪𝘷> React keeps the same DOM node 👉 Only updates class from red → blue 3️⃣ 𝗖𝗵𝗶𝗹𝗱𝗿𝗲𝗻 𝗱𝗶𝗳𝗳𝗶𝗻𝗴 (𝗪𝗵𝗲𝗿𝗲 𝘁𝗵𝗶𝗻𝗴𝘀 𝗴𝗲𝘁 𝗶𝗻𝘁𝗲𝗿𝗲𝘀𝘁𝗶𝗻𝗴 👀) Let’s say: <𝘶𝘭> <𝘭𝘪>𝘈</𝘭𝘪> <𝘭𝘪>𝘉</𝘭𝘪> </𝘶𝘭> ➡️ becomes <𝘶𝘭> <𝘭𝘪>𝘉</𝘭𝘪> <𝘭𝘪>𝘈</𝘭𝘪> </𝘶𝘭> Without keys, React compares index by index: A → B (change) B → A (change) 👉 Result: unnecessary updates ❌ 🔥 Now with keys (real optimization) <𝘶𝘭> <𝘭𝘪 𝘬𝘦𝘺="𝘈">𝘈</𝘭𝘪> <𝘭𝘪 𝘬𝘦𝘺="𝘉">𝘉</𝘭𝘪> </𝘶𝘭> ➡️ becomes <𝘶𝘭> <𝘭𝘪 𝘬𝘦𝘺="𝘉">𝘉</𝘭𝘪> <𝘭𝘪 𝘬𝘦𝘺="𝘈">𝘈</𝘭𝘪> </𝘶𝘭> React uses keys like identity: 👉 “Oh, B moved… A moved” ✅ Reorders instead of re-creating 🚀 Much faster ⚡ Under the hood (what actually happens) • React builds a tree of elements (Fiber tree) • Each update creates a new tree • It walks both trees node by node • Marks changes (called “effects”) • Then applies minimal updates to real DOM 💡 Real-world mental model Think of it like: Old UI → Screenshot 📸 New UI → Screenshot 📸 React = “Spot the difference” game But optimized using rules + keys ⚠️ Common mistakes developers make ❌ Not using keys → causes re-renders ❌ Using index as key → breaks reordering ❌ Changing component type unnecessarily 🚀 Pro Insight React’s diffing is NOT perfect It’s optimized for speed over accuracy 👉 That’s why keys exist — to help React make better decisions Once you understand this, performance optimization in React becomes way easier. Comment “FIBER” if you want that breakdown 👇 #React #ReactDiff #DAY113
To view or add a comment, sign in
-
-
Stop blaming your logic. 🛑 Most React bugs don’t come from missing dependencies or bad code. They come from putting state in the wrong place. Here’s the simple decision tree that changed how I architect React apps 👇 ━━━━━━━━━━━━━━━━━━━━━ 🌳 The State Management Tree 📌 Is this state used by only ONE component? → useState (Keep it local, keep it simple). 📌 Is this state shared between siblings? → Lift it to the parent. (Don't reach for Context just yet!). 📌 Is this data fetched from an API? → React Query or SWR. (Avoid the useState + useEffect trap). 📌 Does this state have complex transitions? (loading → success → error) → useReducer. It makes your logic incredibly readable and testable. 📌 Is this global UI state? (modals, themes, sidebar toggles) → Context API or Zustand. 📌 Is this global server-synced state across the whole app? → Redux Toolkit + RTK Query. ━━━━━━━━━━━━━━━━━━━━━ 💡 The Mistake Most Developers Make They treat ALL state the same. Server data (fetched, cached, async) is fundamentally different from UI state (what's open, what's selected). Mixing them together is a one-way ticket to stale data, unnecessary re-renders, and nasty fetch waterfalls. 🌊 ━━━━━━━━━━━━━━━━━━━━━ ⚡ Bonus: React 18 made this even clearer • startTransition → Tells React: "This update isn't urgent, don't block the user." • useDeferredValue → Keeps the UI responsive while a slow value catches up. • Suspense + lazy() → Splits your bundle to load components only when needed. These aren't just "advanced features." They are React's way of helping you respect the user's experience at an architectural level. ━━━━━━━━━━━━━━━━━━━━━ 🎯 The Golden Rule: Before you type useState, pause and ask: WHY does this state exist, and WHO owns it? That one question will save you hours of debugging. What's your go-to rule for deciding where state lives? Let's discuss below! 👇 #React #ReactJS #Frontend #JavaScript #WebDevelopment #SoftwareEngineering #StateManagement
To view or add a comment, sign in
-
Stop leaving your best performance gains on the table by relying on useState for complex forms. I’ve put together a breakdown of why switching to a schema-driven architecture with React Hook Form + Zod is good practice for modern web apps. Performance Optimization ⦿ Moving to uncontrolled inputs significantly reduces expensive re-render cycles, keeping your UI snappy even in data-heavy environments. Centralized Validation ⦿ By pairing React Hook Form with Zod, you create a single source of truth for business logic, making the codebase easier to maintain and far more predictable. Inclusive Design ⦿ The architecture simplifies accessibility compliance by handling focus management and ARIA states natively. Link to full post: https://lnkd.in/eu74ZW56 #SoftwareEngineering #React #Accessibility #NextJS #Javascript
To view or add a comment, sign in
-
Your frontend is not slow. It’s just doing too much. Let me explain 🧵 Modern frontends quietly became: • State managers • API orchestrators • Cache layers • Validation engines • Offline systems ...and also “the UI” Somewhere along the way, we decided: “Let’s move logic closer to the user” And accidentally moved everything. Now your browser is handling: • business rules • pagination logic • filtering • optimistic updates • retry logic While the server just sits there. This creates a hidden problem: 👉 Two copies of reality. One on the server. One in the browser. And now your app spends more time: syncing state than actually serving users. That’s why you see: • loading spinners everywhere • inconsistent UI states • “works after refresh” bugs • endless edge cases The fix is not: “better state management” The fix is: 👉 less state in the frontend. A better mental model: • URL → shareable state • Session → short-term memory • Database → source of truth • Frontend → just rendering When you do this: • less duplication • simpler code • fewer bugs • faster features The frontend becomes calm again. The browser was never meant to be your backend. We just kept promoting it. Be honest. What’s the most ridiculous thing your frontend is currently responsible for? Read more on why you don’t always need a state management library https://lnkd.in/d_9iksGS
To view or add a comment, sign in
-
𝗬𝗼𝘂 𝗝𝘂𝘀𝘁 𝗡𝗲𝗲𝗱 𝗘𝗻𝘁𝗶𝘁𝗶𝗲𝘀 𝗧𝗵𝗮𝘁 𝗖𝗮𝗻 𝗗𝗶𝗲 Forms break the Single Source of Truth rule. Not because the rule is bad. Because forms are alive. They change with every keystroke. They get submitted and thrown away. We learned this with Redux. Putting form state in the global store felt right. It was a mistake. The mistake was not the global part. It was the forever part. - The store kept form data after submission. - Opening an old form showed stale data. - Every keystroke updated the whole store. Typing felt slow. Formik fixed this. It kept form state inside the component. React Final Form improved it. It let only changed fields re-render. Both hid the problem. They moved form state out of the global store. They did not fix its lifetime. The real choice is not global vs local. It is alive vs dead. - Alive state lives only as long as the form lives. - Dead state lives forever. Redux-form never killed form entities. Formik isolated them. Inglorious Web destroys them. In Inglorious Web, a form is an entity. You create it when needed. You submit it. Then you destroy it. api.notify('remove', entity.id) The state vanishes. No reset needed. No stale data. The form disappears from the UI automatically. This makes forms inspectable. While alive, you see all its state in dev tools. Any other part of the app can read it. No callbacks. No context. For long-lived forms, like a settings page, you just do not destroy the entity. Its state stays until the user leaves. You must decide when things die. This is a visible cost. The alternative is hidden costs like stale data and slow renders. Multi-step forms? Model each step as its own entity. Or use one entity with a step property. Destroy the whole entity when done. The store stays flat. No complex nesting. React Final Form's FormSpy exists because React re-renders parent components on any child state change. You need a subscription system to avoid that. In Inglorious Web, the rendering model is different. A field change updates only that field's DOM node. No virtual DOM. No subscription system needed. Re-renders are cheap. This changes the design. You do not need memoization by default. The framework handles the performance. Redux-form was right about the principle. Wrong about the lifecycle. Formik and React Final Form solved the symptom. They moved the state. The tension between volatile forms and persistent state was never solved. It was hidden. Inglorious Web makes the lifetime explicit. Create an entity. Destroy it when done. The Single Source of Truth principle works fine for forms. You just need entities that can die. Source: https://lnkd.in/gTFVVPep
To view or add a comment, sign in
-
Why Does `useEffect` Run Twice in React 18? You write this: useEffect(() => { console.log("API called"); }, []); But when you open the browser, you see: API called API called And the first thought is: 👉 “Why is React calling my effect twice? Is this a bug?” No. It is intentional. And understanding this can save you hours of confusion. What’s Actually Happening? If you are using React 18 with `StrictMode`, React intentionally runs your effect twice in development. This only happens in development mode. It does NOT happen in production. So in production, the effect runs only once. Why React Does This? React 18 wants to help you find unsafe side effects. To do that, it simulates this process: 1. Mount the component 2. Run the effect 3. Immediately unmount the component 4. Clean up the effect 5. Mount again 6. Run the effect again This helps React detect code that: * Causes memory leaks * Makes duplicate API calls * Creates subscriptions that never clean up * Leaves timers running Example: The Problem React Is Trying To Catch useEffect(() => { const interval = setInterval(() => { console.log("Running..."); }, 1000); }, []); Looks fine, right? But there is a hidden problem: The interval never gets cleared. So if the component unmounts and mounts again, multiple intervals keep running. That is exactly the kind of issue Strict Mode wants you to notice. Many developers think: ❌ “Let me remove StrictMode.” But that is not the right solution. StrictMode is helping you catch bugs early. Instead, make your effect safe. For example: useEffect(() => { const controller = new AbortController(); fetch("/api/users", { signal: controller.signal, }) .then((res) => res.json()) .then((data) => console.log(data)) .catch((err) => { if (err.name !== "AbortError") { console.error(err); } }); return () => controller.abort(); }, []); Now if the first request is interrupted during unmount, React cleans it up properly. So if your effect runs twice only in development, do not panic. That is expected behavior. The Biggest Mistake Developers Make Many developers try this: const hasRun = useRef(false); useEffect(() => { if (hasRun.current) return; hasRun.current = true; console.log("Runs once"); }, []); Yes, this prevents the second run. But it hides the real problem instead of fixing it. If your effect breaks when React mounts twice, the effect is not safe enough. The goal is not to stop React. The goal is to write effects that can safely run multiple times. Best Practices for `useEffect` * Always clean up subscriptions, intervals, and event listeners * Abort API requests when needed * Keep effects small and focused * Don’t put everything inside one giant `useEffect` * Don’t remove `StrictMode` just to avoid double execution #React #ReactJS #JavaScript #Frontend #WebDevelopment #useEffect #Programming #SoftwareEngineering
To view or add a comment, sign in
-
-
The "Ghost in the API": How I fixed a major rendering lag 👻 While working on a complex user dashboard at Codes Thinker, I encountered a frustrating performance bottleneck. Every time a user triggered a data fetch, the entire UI would "freeze" for a split second before updating. Even with a fast backend API, the user experience felt "heavy" and unprofessional. The Challenge: We were fetching large, nested JSON objects directly inside a parent component. Every time the API responded, the entire component tree re-rendered, causing a visible performance lag during data transformation. The Solution: React Query: I implemented React Query to handle caching. This ensured that if a user requested the same data twice, the result was instant. Data Transformation: Instead of passing the raw "heavy" object to components, I mapped the data into a lighter format immediately after fetching. Optimistic UI: I used Tailwind CSS to create smooth skeleton loaders, making the app feel faster while the data was still loading. The Result: The rendering lag disappeared, and the user experience became fluid. Sometimes, being a Senior Frontend Developer is about knowing when not to fetch data as much as how to fetch it. Have you ever faced a stubborn API lag? How did you tackle it? Let’s share some dev stories! 👇 #RESTAPI #NextJS #PerformanceOptimization #MERNStack #WebDevelopment #CleanCode #ReactJS #TailwindCSS
To view or add a comment, sign in
-
-
🔍 React 19.2: 3 anti-patterns now visible like never before You asked for a follow-up. Here it is. In the last post, I explained how DevTools in React 19.2 show the real reason behind every re-render. Today, I'm showing you 3 anti-patterns you used to ignore. But not anymore. Let's go. 1️⃣ Anonymous objects in props ❌ How many write: <div style={{ margin: 10, padding: 20 }}> ✅ How to fix: const boxStyle = { margin: 10, padding: 20 }; <div style={boxStyle}/> Why? A new object is created every time. React thinks: "Oh, props changed! Need to re-render!" In DevTools you'll see: "Style prop changed: new object created" 2️⃣ useEffect with no dependencies ❌ How many write: useEffect(() => { setCount(count + 1); }); ✅ How to fix: useEffect(() => { setCount(prev => prev + 1); }, []); Why? Without the dependency array, the effect runs after EVERY render. Hello, infinite loop. In DevTools you'll see: "Infinite render loop detected" 3️⃣ Custom hook returning a new function ❌ How many write: function useToggle() { const [value, setValue] = useState(false); return { value, toggle: () => setValue(v => !v) }; } ✅ How to fix: function useToggle() { const [value, setValue] = useState(false); const toggle = useCallback(() => setValue(v => !v), []); return { value, toggle }; } Why? Without useCallback, a new function is created on every call. The consumer component re-renders for no reason. In DevTools you'll see: "Toggle function changed: new reference" 📊 Summary 🔹 Anti-pattern #1 — Anonymous objects in props → Solution: extract object to a constant 🔹 Anti-pattern #2 — useEffect with no dependency array → Solution: add empty array [] 🔹 Anti-pattern #3 — New function on every render → Solution: wrap with useCallback 💬 Question for you Which of these three anti-patterns do you see most often in your codebase? Mine was #1. What about you? Drop your anti-patterns in the comments. #React19 #ReactJS #CleanCode #CodeReview #JavaScript #WebDev #ReactHooks #DevTools
To view or add a comment, sign in
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development
For Rule 2 - I would add that it’s okay to use indexes as long as the array is static, using unique identifiers as keys is usually done for arrays that come from API endpoints