What Is a UI Component?
A UI Component is a single function that creates elements, sets their content, attaches handlers, and paints the screen -- all driven by one source of truth.
In the previous post, we built a dataToView function that turned our post variable into visible content every 15 milliseconds. It was a big step forward. One function, one source of truth, no mystery.
But we missed something. We had input and div sitting in our HTML from day one. They were always there, silently assumed to exist. When a user typed a certain name into the input, our handler removed the div by calling jsDiv.remove(). The element vanished, but that vanishing happened inside a handler, not inside dataToView.
We had just broken our only rule.
Elements are view too
Think about what a div appearing or disappearing on the screen is. It is a change the user sees. It is content. And if content must be driven by underlying data, then the very existence of a div or an input needs to be driven by data as well.
We started with the assumption that input and div were permanently there. But "permanently there" is just an implicit piece of state. If we ever want to turn them off based on data, we need to make that state explicit.
The fix is to move element creation out of the HTML and into dataToView. Instead of using document.querySelector to get a reference to an element HTML already created, we use document.createElement to build one from scratch every time.
function dataToView() {
// Create from scratch -- conditional on data
let jsInput = document.createElement('input');
let jsDiv = post !== 'will'
? document.createElement('div')
: ''; // no div if post is 'will'
jsInput.value = post;
jsDiv.textContent = post;
jsInput.oninput = handleInput;
// Replace whatever is on the page with what the data says should be there
document.body.replaceChildren(jsInput, jsDiv);
}replaceChildren is the key move here. Instead of carefully updating specific parts of the DOM based on what changed, we describe the complete picture from scratch and swap the whole thing in. We do not track what was there before. We just say: "Given my current data, here is the page. Replace everything with this."
This is brute force. But it is also completely predictable.
ExpandA UI Component owns everything the user sees
The handler only changes data
With dataToView now fully owning the DOM, our handler gets a lot simpler:
function handleInput() {
post = jsInput.value;
// That's it. We don't touch the DOM here.
}The handler's only job is to change post. Once post changes, dataToView runs again in the next 15ms window and the page updates automatically. The handler never reaches into the DOM. It never decides what to show or not show. It just changes the data and steps back.
One small catch: focus
Because we call replaceChildren and hand the browser a brand new input element every 15 milliseconds, the user's cursor gets kicked out of the input field constantly. We need one extra line:
jsInput.focus();This puts the cursor back into the input after every replacement, so the user can keep typing without noticing anything happened behind the scenes.
That function is a component
What we have built has a name. A UI Component is a function that:
- Creates elements (conditional on data)
- Sets their content (conditional on data)
- Attaches event handlers
- Renders the complete picture to the page via
replaceChildren
It fully describes the relationship between data and what the user sees, under every possible state the data could be in. You want to know what the page looks like when post is empty? Read dataToView. You want to know what it looks like when post is "hello"? Read dataToView. The function is the complete truth.
The handler, meanwhile, is just a submission mechanism. The user takes an action, the handler processes that action into a data change, and then dataToView takes it from there.
let post = '';
function dataToView() {
let jsInput = document.createElement('input');
let jsDiv = post ? document.createElement('div') : '';
jsInput.value = post;
if (jsDiv) jsDiv.textContent = post;
jsInput.oninput = handleInput;
jsInput.focus();
document.body.replaceChildren(jsInput, jsDiv);
}
function handleInput() {
post = jsInput.value;
}
setInterval(dataToView, 15);This is a complete UI component. Around 50 lines of conceptual model, made simple. Every major UI framework -- React, Vue, Angular, Svelte -- is built on this exact idea. The details differ. The principle does not.
The cost of brute force
There is one large problem with this approach. We are rebuilding the entire DOM structure every 15 milliseconds, regardless of whether anything actually changed. If our page has 1,000 elements, we are creating 1,000 DOM nodes, attaching 1,000 handlers, and calling replaceChildren with 1,000 children -- 66 times a second.
That is an enormous amount of work for the browser. Real applications would grind to a halt.
In the next post, we look at how to solve this by keeping a lightweight JavaScript description of what the page should look like, and only pushing the actual differences to the real DOM.
Further Reading and Watching
- Video: React in 100 Seconds -- Fireship
- Article / Docs: Thinking in React -- React Docs
Keep reading