What React Adds to a Page
Do you think React is running your whole webpage?
I think a lot of React discussions make it sound that way. You install React, call createRoot(), read a couple tutorials and after some time you can easily assume every single thing you see on the page is controlled by React.
Yeah... no. Browser was already doing a lot of work before any React code even ran.
Browser can parse HTML, apply CSS styles, run JavaScript, handle clicks and keyboard input, submit forms, follow links, make HTTP requests, create DOM nodes, remove them, change their text, repaint the page, etc. None of that came from React.
So then what did we install React for? Why do people even use it?
React gives us the "component model". This may sound a bit vague right now, but we'll get there. We write JavaScript functions that return descriptions of UI, i.e. how the UI should look based on the current data of our app. That description can depend on props, state, context, and whatever other values the component reads while rendering. When something changes and React decides another render is needed, the component runs again and returns another description.
Then React DOM handles the browser DOM side of this. It takes the output React calculated and changes the actual DOM nodes which need changing.
So in a normal React website there's still multiple systems doing different jobs. Browser still does all the normal browser stuff, React handles components and rendering them, React DOM connects that output with the actual browser DOM, and then you probably also have something such as Vite handling JSX, modules, dev server, builds and all that.
We should separate these properly first, otherwise later you start giving React credit for things the browser was doing perfectly fine since long before React existed.
What the browser already does
Forget React for a minute. We don't need it here.
Take this HTML:
<main>
<h1>ReactBook</h1>
<p>0 lessons complete</p>
<button type="button">Complete one lesson</button>
</main>You open this file in the browser. What happens?
Browser reads the HTML text, parses it, and creates objects in memory for all these elements. There's an object for main, another one for h1, p, button, text nodes inside them, etc. All these live objects are part of the Document Object Model, or DOM.
There's one difference here you should be very clear about. Your HTML file and DOM are not the same thing.
The HTML file sitting on disk is just text. Browser reads that text and creates the live document in memory. That live document is what JavaScript works with after the page has loaded.
So if we do this:
const output = document.querySelector("p");
output.textContent = "1 lesson complete";we did not edit the HTML file on disk. document.querySelector("p") searches the current DOM and returns the paragraph object, and then assigning to textContent changes what's inside that DOM node. Browser then updates what you see on screen.
No React anywhere.
Same with CSS.
button {
background: #61dafb;
color: #06141b;
}Browser matches the selector against elements, computes the styles, works out layout and eventually paints the button. React can set a className, or you can give some style properties through JSX, but React itself isn't sitting there calculating CSS layout.
Browser still does that job.
Events also work perfectly fine without React
You don't need React for clicks either.
We can make that button work using plain JavaScript:
let completed = 0;
button.addEventListener("click", () => {
completed += 1;
output.textContent = `${completed} lessons complete`;
});completed is just a variable sitting in memory. Browser sees the click and runs our listener. We increment the variable, then manually change the paragraph in the DOM.
For a small script this is completely fine.
But now let's say this same value is shown in three places:
output.textContent = `${completed} lessons complete`;
progress.value = completed;
button.disabled = completed >= total;Still not too bad, right? Whenever completed changes, update these three things.
Then some more code gets added. Maybe another button can change completed. Maybe we load progress from the server. Maybe there's a reset button. Maybe finishing one lesson automatically changes it.
Now every place which changes completed also needs to remember all the DOM elements which depend on it.
Forget one of those lines somewhere and your data says one thing while the page says another. Maybe the paragraph says you've completed everything but the button is still enabled. Nothing has crashed, no error in console, page is just wrong. Fun.
This coordination between data and UI is one of the main reasons React becomes useful.
So what does React actually add?
With React, instead of manually changing those three DOM elements every time completed changes, we can write a component which says what the whole UI should look for the current values.
function Progress({ completed, total }) {
return (
<section>
<p>{completed} lessons complete</p>
<progress value={completed} max={total} />
<button disabled={completed >= total}>Complete one lesson</button>
</section>
);
}Now look at where the data is being used. Paragraph gets completed, progress gets completed and total, and button gets completed >= total.
When React renders this component again with another completed value, all these expressions run again and React gets another description of what this section should look.
You don't need to go through your click handler and manually say update paragraph, then progress, then button. The component already describes all three using the current values.
This is what people are talking about when they say React is declarative. You say what the output should be for the current data instead of writing DOM commands for every individual change.
Also notice something about Progress(). It doesn't search the page, grab an existing <p>, call document.createElement(), or directly disable some existing button. It just returns React elements describing what should exist for these values.
React does the component calculation, then React DOM deals with changing the actual browser DOM.
Why are react and react-dom separate?
Your component isn't sitting there running forever.
React calls it when it needs to render that component. That happens on the initial render, after certain state updates, when its parent renders it again, when context it reads changes, and other cases where React needs fresh output from that component.
Each time the function runs, it sees the props and state for that render and returns UI based on those values.
And this also explains why a web React project installs two packages:
{
"dependencies": {
"react": "^19.2.0",
"react-dom": "^19.2.0"
}
}The react package gives you components, Hooks, context, React elements, transitions and the rest of React's component stuff. It doesn't go calling document.createElement() itself to build browser HTML.
For web apps, react-dom handles that side.
import { createRoot } from "react-dom/client";That /client entry gives us APIs for connecting a React tree to a DOM container in the browser.
React DOM also has server-side APIs for producing HTML from React trees, which is why you will see different entry points depending on where the rendering is happening.
And this split is also why React itself can be used outside a browser. React Native uses React's component system too, but its renderer creates native mobile UI rather than HTML DOM elements.
Same React component model, different renderer.
React only controls the root you give it
Does createRoot() suddenly make React owner of your entire HTML document?
Nope.
React DOM needs an existing DOM element first:
<div id="root"></div>Then we find that element and create a React root inside it:
const container = document.getElementById("root");
const root = createRoot(container);From here React manages whatever we render inside that root.
Stuff outside it can stay plain HTML.
<header>Server-rendered site header</header>
<div id="lesson-planner"></div>
<footer>Server-rendered site footer</footer>If we create our root using #lesson-planner, React only manages the content rendered inside that element. Header and footer don't suddenly become React components just because there's some React code elsewhere on the page.
You can have multiple React roots too. Maybe one part of an older website has a React lesson planner and another part has a React account widget. Both can exist separately.
Those roots are separate React trees though, so don't expect them to magically share one React context or one parent component tree.
Also, once React is managing the children inside a root, don't have some random vanilla JavaScript changing those same children using innerHTML, appendChild() and similar DOM APIs. React keeps its own information about what it rendered there, and now another script has gone and changed those nodes separately.
React can then be working with one idea of what's there while the DOM contains something else. Bugs after this can get pretty weird.
Render and commit
You'll hear these two words a lot in React: render and commit.
During render, React calls your components and calculates what they return for the current data. This work is happening in JavaScript. React is figuring out the next UI output.
Then comes commit. This is when React DOM applies the required changes to the actual DOM. Text may need changing, some element may need to be inserted, some attribute may have changed, or maybe an old element needs removing.
Roughly:
update requested
-> React calculates component output
-> React DOM applies required DOM changes
-> browser updates what gets displayedAnd browser still handles the final browser work after those DOM changes. React isn't drawing the button pixels itself.
This is also why doing random side effects inside the component body causes problems. The component body runs as part of React calculating output, and React can run that code more than you may expect.
So don't do stuff such as changing DOM nodes, starting HTTP requests, or writing to localStorage directly just because the component function ran. Depending on what you're doing, that work usually belongs in an event handler or an Effect.
React may also call component functions additional times during development to help expose code which isn't safe to run during rendering, so having side effects mixed into that calculation gets annoying very quickly.
A component can render without changing the DOM
This is another thing which sounds weird initially.
A render happened. Surely something changed on the page?
Not necessarily.
Take this component:
function Status({ online }) {
return <p>{online ? "Online" : "Offline"}</p>;
}Suppose online is true.
React runs the component and gets a paragraph containing Online.
Later something causes this component to render again, but online is still true. The function runs again, it again describes a paragraph containing Online, and React DOM can see the existing DOM already has the result it needs.
So there may be nothing to change.
The component ran, React calculated output, but no DOM text needed updating.
That's why "render" and "DOM update" shouldn't mean the same thing in your head. React can run component code even when the visible browser output stays exactly the same.
And during the commit phase React may inspect what needs applying and end up with very little, or nothing, to mutate for some parts of the tree.
Your component name doesn't become an HTML tag
Suppose we write this:
function SaveButton() {
return <button type="button">Save</button>;
}and then somewhere else:
<SaveButton />Does the browser now contain a <SaveButton> HTML element?
No.
React DevTools can show you a SaveButton component because that's part of your React component tree. But open the normal Elements panel in DevTools and what you'll actually see is:
<button type="button">Save</button>SaveButton ran and returned a React element describing a normal button. React DOM then created or updated that actual <button> in the DOM.
This is also why capitalization in JSX has meaning. Something uppercase such as <SaveButton /> tells React you're referring to a JavaScript component. Something lowercase such as <button /> refers to the actual host element the renderer knows how to create.
So your React component tree and browser DOM tree are not identical. DevTools can show a React component which never exists as a real HTML tag.
Where does React stop?
People sometimes install React and then start associating half the application with React itself.
React doesn't give you URL routing, authentication, database connections, production builds, hosting, server setup or some automatic data caching system just because you installed the package.
Those come from other tools and libraries.
Maybe Vite handles your dev server and build. Maybe React Router handles client-side URLs. Maybe you're using a framework which handles server rendering and routing. Your backend talks to the database. Some other library may handle server data caching.
React itself is mostly concerned with components and rendering their UI.
It also doesn't change normal HTML behavior.
If your component returns:
<button type="button">Save</button>React DOM creates a real <button> element. Browser already knows how buttons behave, including keyboard interaction, focus behavior, button semantics and form-related behavior.
Now if you do this:
<div onClick={save}>Save</div>React can attach the click handling, sure, but browser does not start treating this div as a normal button. You don't automatically get the same keyboard and form behavior just because you put an onClick on it.
HTML still works the way HTML works.
When something breaks, first figure out who's doing that job
This separation becomes pretty useful once you're debugging.
Say your #root element doesn't exist. That's something to check in the HTML document or whatever generated that document, not inside some random component.
If JSX won't compile, look at your build setup and JSX transform. If a component is getting the wrong prop, now you're looking at React data flow. If the element exists but the styles are wrong, open computed styles and inspect CSS. If your clickable thing cannot be focused using Tab, check what HTML element you're actually rendering.
Same thing when state changes but the screen doesn't show what you expected. Now you start looking at the component, state update, maybe a stale value somewhere, maybe memoization, maybe some logic which isn't doing what you thought.
Asking "which system is responsible for this?" before changing code saves a lot of random debugging.
From loading HTML to seeing the React page
Let's put the whole thing together once.
Browser requests index.html and parses it. That HTML contains some element React can use as its root, and usually a module script which starts your application.
Your JavaScript runs, finds that DOM element and passes it to createRoot().
React then starts rendering your components and calculates the initial UI description. React DOM takes that result and creates the required browser DOM nodes inside the root.
After the DOM has been updated, browser does the browser work: styles, layout, painting and showing the result to you.
So browser still owns HTML parsing, the real DOM, CSS, native events, layout and painting. React runs your components and calculates what UI they describe for the current data. React DOM takes that React output and makes the required changes to the browser DOM.
Once you stop grouping all three of these together as "React", a lot of things suddenly become less confusing.
React didn't replace the browser. It added a component system for describing UI from your JavaScript state, and React DOM is the browser renderer which takes those descriptions and updates the actual document.