React DOM is the part that makes your React components show up in browsers. React itself just handles the component logic - React DOM takes that and dumps it into the actual browser DOM. You need both packages: npm install react react-dom
. The React documentation explains this separation in detail, and the React DOM reference covers all the available APIs.
The Basics That Actually Matter
React DOM converts your JSX into real HTML elements that browsers understand. When you write <div>Hello World</div>
in React, that's just a JavaScript object until React DOM processes it and creates an actual <div>
element in the browser.
React applications are structured as component trees where parent components pass data down to children through props. This hierarchical structure allows for predictable data flow and component composition patterns.
The component lifecycle shows how React DOM manages components from creation to destruction, handling mounting, updating, and unmounting phases automatically.
Client-Side Rendering: The `createRoot` API is how you start a React app in the browser. They changed this from ReactDOM.render
in React 18, which broke half our fucking components when we upgraded. The migration was annoying because they changed the API for no obvious reason. The React 18 upgrade guide covers this change, and the concurrent features documentation explains why they made it. The createRoot migration guide provides step-by-step instructions:
// Old way (React 17 and below) - DEPRECATED
ReactDOM.render(<App />, document.getElementById('root'));
// New way (React 18+) - breaks if you forget the import
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
Server-Side Rendering: SSR sounds great until you're debugging hydration mismatches during a production outage. React DOM has `renderToPipeableStream` for Node.js and `renderToReadableStream` for edge runtimes. The streaming stuff is nice when it works, but hydration errors are cryptic as hell. The SSR documentation covers all server APIs, while the streaming guide explains how to stream HTML progressively.
The virtual DOM reconciliation process shows how React DOM compares component trees and updates only the parts that changed. This diffing algorithm enables efficient updates by calculating the minimum set of changes needed to update the real DOM.
Hydration: The `hydrateRoot` function is supposed to take server-rendered HTML and make it interactive. In reality, hydration mismatches will waste hours of your debugging time with error messages like "Text content does not match server-rendered HTML" that tell you absolutely nothing useful about what's actually wrong. The hydration documentation provides the API details, and the troubleshooting guide lists common issues.
We had a hydration mismatch that only happened on iPhones with dark mode enabled - took 3 days in February 2024 to figure out it was a third-party calendar widget modifying DOM timestamps differently on iOS. Cost us a weekend and nearly got me fired.
Performance Reality Check
React DOM has gotten way faster over the years, especially with the concurrent stuff in React 18. The virtual DOM diffing is solid, and React 19's concurrent features let rendering get interrupted so long updates don't freeze the UI.
Bundle size still sucks though - the combined weight clocks in at over 40KB compressed, which murders mobile performance unless you code-split properly. Performance varies wildly depending on your component structure and state management choices.
React 19 New Stuff
React 19 added some useful features that actually solve real problems:
Document metadata: Components can now include
<title>
and<meta>
tags that get hoisted to the document head automatically. No more manually stuffing metadata into<head>
.Stylesheet management: Better CSS loading order so your styles don't flash unstyled content.
Resource preloading: New preload functions so you don't have to manually manage
<link rel="preload">
tags.
Look, I've been dealing with React DOM's quirks since 2017, and while it's gotten better, debugging production React issues still makes me want to throw my laptop out the window.
The concurrent features in React 18 are legitimately useful though - time slicing prevents those janky UI freezes we used to get. The React 18 migration in March 2022 broke our payment flow for a week because Stripe's React components weren't compatible with concurrent mode. Fun times.
React Fiber's architecture enables incremental rendering and prioritization, allowing React DOM to interrupt rendering work to handle higher-priority updates. This allows React to break rendering work into chunks and prioritize urgent updates like user inputs over less critical background updates.
The main problem with React DOM is debugging production issues. Error boundaries are nice in theory, but when shit breaks in prod, you're still stuck with cryptic stack traces and unhelpful error messages. React DevTools helps but crashes Chrome when your component tree gets huge.