[HN Gopher] JavaScript Views, the Hard Way - A Pattern for Writi...
       ___________________________________________________________________
        
       JavaScript Views, the Hard Way - A Pattern for Writing UI
        
       Author : voat
       Score  : 172 points
       Date   : 2025-04-19 02:10 UTC (20 hours ago)
        
 (HTM) web link (github.com)
 (TXT) w3m dump (github.com)
        
       | zffr wrote:
       | The read me says this approach is extremely maintainable, but I'm
       | not sure I agree.
       | 
       | The design pattern is based on convention only. This means that a
       | developer is free to stray from the convention whenever they
       | want. In a complex app that many developers work on concurrently,
       | it is very likely that at least one of them will stray from the
       | convention at some point.
       | 
       | In comparison, a class based UI framework like UIKit on iOS
       | forces all developers to stick to using a standard set of APIs to
       | customize views. IMO this makes code way more predictable and
       | this also makes it much more maintainable.
        
         | netghost wrote:
         | Convention works when the culture is there, but I think you're
         | right a dash of typescript and a class or interface definition
         | could go a long ways.
         | 
         | I think the maintainability comes from easy debugging. Stack
         | traces are sensible and the code is straightforward. Look at a
         | React stack trace and nothing in the trace will tell you much
         | about _your_ code.
         | 
         | I'd also point out that this looks like it's about seven years
         | old. We've shifted a lot of norms in that time.
        
         | _heimdall wrote:
         | Any code base lives or dies by how well it defines and then
         | sticks to conventions. We can enforce it in different ways, or
         | outsource the defining of convention to other tools and
         | libraries, but we still have to use them consistently in the
         | codebase.
         | 
         | I think the OP here is basically proposing that the developer
         | should be directly responsible for the conventions used. IMO
         | that's not a bad thing, yes it means developers need to be
         | responsible for a clean codebase but it also means they will
         | better understand _why_ the conventions exist and _how_ the app
         | actually works. Both of those are easily lost when you follow
         | convention only because a tool or library said that 's how its
         | done.
        
           | nonethewiser wrote:
           | Using a framework like react constrains developers in a
           | different way. React isnt simply a convention like the linked
           | example.
        
             | _heimdall wrote:
             | I see it differently there, react (any framework) is simply
             | convention built into shared libraries and enforced through
             | tooling.
             | 
             | React is a particularly interesting one because it is still
             | flexible enough that there is still a lot of reliance on
             | developers actively sticking to the conventions
             | recommended.
        
       | atum47 wrote:
       | On my first official job after college I was working on making a
       | web version of a Delphi software. The team was already on their
       | third rewrite of the front end cause they had to change
       | frameworks. I made the cass that we should write our own
       | framework, so I prototyped FOS (the components I use on my
       | website) to prove my point. The team (a bunch of mostly Delphi
       | programmers) did not like my suggestion. Anyways, soon after that
       | another company made me a better offer so I left. Years went by
       | an I finally take a shot at another framework: tiny.js [1]. I've
       | been using it in all my personal projects so far. I'm particular
       | proud of the ColorPicker [2] component I wrote that I've used in
       | two projects so far. As you can see, one can argue that tiny.js
       | it's not a framework at all, just some wrapper functions that
       | helps you create Functional components.
       | 
       | 1 - https://github.com/victorqribeiro/TinyJS
       | 
       | 2 -
       | https://github.com/victorqribeiro/Chip8js/blob/master/js/Col...
        
         | npodbielski wrote:
         | I dont know... I kind of like diffrent look of HTML and JS. At
         | least you know what is what. In tiny evrything looks like JS
         | and you actually have to read it to know what is what. Also
         | what if someone will define span variable? Does it override the
         | span HTML component function?
         | 
         | Otherwise looks like nice.
        
           | atum47 wrote:
           | In tiny evrything looks like JS and you actually have to read
           | it to know what is what
           | 
           | You don't, actually. If in HTML you write
           | <select><option/><select/> in tiny you write select(option())
           | Also what if someone will define span variable?
           | 
           | I'm guilty of that myself. Tried to name a variable input
           | when there's already a function with that name. It forces me
           | to come up with better descriptive names. I could've wrapped
           | those functions inside namespace like tiny.input() but I like
           | the simplicity of it as is.
        
         | hu3 wrote:
         | You might want to look at something like morph Dom to keep
         | input focus when you have to re-render the form, for example.
        
           | atum47 wrote:
           | Since the user decides what happens when the state gets
           | updated its up to them to address that. For me, I usually
           | avoid re-renders when possible, I rather update the property
           | associated with the state.
           | 
           | Have you faced any scenarios where that's needed? I'm
           | curious.
        
       | lylejantzi3rd wrote:
       | I came up with something similar recently, except it doesn't use
       | template elements. It just uses functions and template literals.
       | The function returns a string, which gets dumped into an existing
       | element's innerHTML. Or, a new div element is created to dump it
       | into. Re-rendering is pretty quick that way.
       | 
       | A significant issue I have with writing code this way is that the
       | functions nest and it becomes very difficult to make them compose
       | in a sane way.                   function printPosts(posts) {
       | let content = ""                posts.forEach((post, i) => {
       | content += printPost(post)           })
       | window.posts.innerHTML = content         }              function
       | printPost(post) {           return `             <div
       | class="post" data-guid="${post.guid}">               <div>
       | <img class="avatar"
       | src="https://imghost.com${post.avatar.thumb}"/>
       | </div>               <div class="content">                 <div
       | class="text-content">${post.parsed_text}</div>
       | ${post?.image_urls?.length > 0 ?
       | printImage(`https://imghost.com${post.image_urls[0].original}`) :
       | ''}                 ${post?.url_preview ? `<hr/><div
       | class="preview">${printPreview(post.url_preview)}</div>` : ''}
       | ${post?.quote_data ? `<hr/><div
       | class="quote">${printQuote(post.quote_data)}</div>` : ''}
       | ${post?.filtered ? `<div>filtered by:
       | <b>${post.filtered}</b></div>` : ''}               </div>
       | </div>           `         }
        
         | hyperhello wrote:
         | I like it. Not only does it move the UI into JavaScript, but it
         | moves the scripting into the HTML!
        
           | Koffiepoeder wrote:
           | Have a feeling this will lead to XSS vulnerabilities though.
        
         | MrJohz wrote:
         | How do you update the html when something changes? For me,
         | that's the most interesting question for these sorts of micro-
         | frameworks - templating HTML or DOM nodes is super easy, but
         | managing state and updates is hard.
        
           | dleeftink wrote:
           | I find the coroutine/generator approach described in a series
           | of posts by Lorenzo Fox/Laurent Renard to be a promising
           | alternative[0].
           | 
           | It takes a little to wrap your head around, but essentially
           | structures component rendering to follow the natural
           | lifecycle of a generator function that takes as input the
           | state of a previous yield, and can be automatically cleaned
           | up by calling `finally` (you can observe to co-routine state
           | update part in this notebook[1]).
           | 
           | This approach amounts to a really terse co-routine
           | microframework [2].
           | 
           | [0]: https://lorenzofox.dev/posts/component-as-infinite-
           | loop/#:~:...
           | 
           | [1]: https://observablehq.com/d/940d9b77de73e8d6
           | 
           | [2]: https://github.com/lorenzofox3/cofn
        
           | lylejantzi3rd wrote:
           | I call printPosts with the new post data. It rewrites the
           | whole chunk in one go, which is pretty snappy. I haven't
           | decided how I'm going to handle more granular updates yet,
           | like comment count or likes.
        
             | MrJohz wrote:
             | Yeah, that's a pretty common approach. Unfortunately,
             | browsers aren't very good at doing patch updates, so it'll
             | completely reset any UI elements in the region being
             | rerendered.
             | 
             | It also will make it hard to scope anything you want to do
             | to an individual DOM element. If you want granular updates,
             | for example, you want to be able to do something like
             | `document.querySelector(???)` and be certain it's going to
             | refer to, say, a specific text input in your `printPost`
             | template, without worrying about accessing the inputs
             | created by other instances of the `printPost` template. You
             | can do that with unique IDs, but it's fiddly and error-
             | prone.
        
         | econ wrote:
         | I prefer something like this before building the template
         | string.
         | 
         | image = post.image_urls?[0] || "";
         | 
         | Then have the printImage function return an empty string if the
         | argument is an empty string.
         | 
         | ${printImage(image)}
         | 
         | Easier on the eyes.
        
         | chrismorgan wrote:
         | This is _begging_ for injection attacks. In this case, for
         | example, if parsed_text and filtered can contain  < or &, or if
         | post.guid or post.avatar.thumb can contain ", you're in
         | trouble.
         | 
         | Generating serialised HTML is a mug's game when limited to
         | JavaScript. Show me a mature code base where you have to
         | remember to escape things, and I'll show you a code base with
         | multiple injection attacks.
        
           | foota wrote:
           | Yeah, OPs code is asking for pain. I suspect there are now
           | developers who've never had to generate html outside the
           | confines of a framework and so are completely unaware of the
           | kinds of attacks you need to protect yourself against.
           | 
           | You can do it from scratch, but you essentially need to track
           | provenance of strings (either needs to be escaped and isn't
           | html, e.g., user input, or html, which is either generated
           | and with escaping already done or static code). It seems like
           | you could build this reasonably simply by using tagged
           | template literals and having e.g., two different Types of
           | strings that are used to track provenance.
        
             | brigandish wrote:
             | Thus recreating Perl's taint mode. Everything new is old.
        
           | lylejantzi3rd wrote:
           | Posts are sanitized on the server side. This is client side
           | code.
        
             | hombre_fatal wrote:
             | Server-side sanitization means that your view code is
             | inherently vulnerable to injection. You'll notice in modern
             | systems you don't sanitize data in the database and you
             | don't have to manually sanitize when rendering frontend
             | code. It's like that for a reason.
             | 
             | Server-side sanitization and xss injection should be left
             | in the 2000s php era.
        
               | jdsleppy wrote:
               | Where do you suggest we sanitize values? Only in the
               | client, when rendering them?
        
               | chrismorgan wrote:
               | Depends on what you mean by sanitising.
               | 
               | If you mean filtering out undesirable parts of a document
               | (e.g. disallowing <script> element or onclick attribute),
               | that should normally be done on the server, before
               | storage.
               | 
               | If instead you mean _serialising_ , writing a value into
               | a serialised document: then this should be done at the
               | point you're creating the serialised document. (That is,
               | where you're emitting the HTML.)
               | 
               | But the golden standard is not to generate serialised
               | HTML manually, but to generate a DOM tree, and serialise
               | that (though sadly it's still a tad fraught because HTML
               | syntax is such a mess; it works better in XML syntax).
               | 
               | This final point may be easier to describe by comparison
               | to JSON: do you emit a JSON response by writing `{`, then
               | writing `"some_key":`, then writing `[`, then writing
               | `"\"hello\""` after carefully escaping the quotation
               | marks, and so on? You can, but in practice it's very
               | rarely done. Rather, you create a JSON document, and then
               | serialise it, e.g. with JSON.stringify inside a browser.
               | In like manner, if you construct a proper DOM tree, you
               | don't _need_ to worry about things like escaping.
        
               | juliend2 wrote:
               | What's wrong about filtering before saving, is that if
               | you forget about one rule, you have to go back and re-
               | filter already-saved data in the db (with some one-off
               | script).
               | 
               | I think "normally" we should instead filter for XSS
               | injections when we generate the DOM tree, or just before
               | (such as passing backend data to the frontend, if that
               | makes more sense).
        
               | zdragnar wrote:
               | Don't forget that different clients or view formats
               | (apps, export to CSV, etc) all have their own
               | sanitization requirements.
               | 
               | Sanitize at your boundaries. Data going to SQL? Apply SQL
               | specific sanitization. Data going to Mongo? Same. HTML,
               | JSON, markdown, CSV? Apply the view specific sanitizing
               | on the way.
               | 
               | The key difference is that, if you deploy a JSON API that
               | is view agnostic, that the client now needs to apply the
               | sanitization. That's a requirement of an agnostic API.
        
             | chrismorgan wrote:
             | Although appealing, that's an extremely bad idea, when
             | you're limited to JavaScript. In a language with a better
             | type system, it can be only a very bad idea.
             | 
             | The problem is that different contexts have different
             | escaping rules. It's _not possible_ to give a one-size-
             | fits-all answer from the server side. It has to be done in
             | a context-aware way.
             | 
             | Field A is plain text. Someone enters the value "Alpha &
             | Beta". Now, what does your server do? If it sanitises by
             | stripping HTML characters, you've just blocked valid input;
             | not good. If it doesn't sanitise but instead
             | unconditionally escapes HTML, somewhere, sooner or later,
             | you're going to end up with an "Alpha &amp; Beta" shown to
             | the user, when the value gets used in a place that _isn't_
             | taking serialised HTML. It always happens sooner or later.
             | (If it doesn't sanitise or escape, and the client doesn't
             | escape but just drops it directly into the serialised HTML,
             | that's an injection vulnerability.)
             | 
             | Field B is HTML. Someone enters the value "<img src=/
             | onerror=alert('pwnd')>". Now, what does your server do? If
             | it sanitises by applying a tag/attribute whitelist so that
             | you end up with perhaps "<img src="/">", fine.
        
               | krapp wrote:
               | Server-side templating frameworks had context-aware
               | escaping strategies for years before front end frameworks
               | were even a thing. Injection attacks don't persist
               | because this is a hard problem, they persist because
               | security is not a priority over getting a minimum viable
               | product to market for most webdev projects.
               | 
               | The old tried and true strategy of "never sanitize data,
               | push to the database with prepared statements and escape
               | in the templates" is basically bulletproof.
        
               | naasking wrote:
               | You're unnecessarily complicating this. The server is
               | aware of what fields are HTML so it just encodes the data
               | that it returns like we've been doing for 30 years now.
               | If your point is that this approach is only good with
               | servers that you trust, then that's useful to point out,
               | although we kind of already are vulnerable to server
               | data.
        
         | spankalee wrote:
         | You should really check out lit-html[1]. It's not a framework
         | like this README claims. It just renders template with template
         | literals, but it does so with minimal DOM updates and safely.
         | And it has a number of features for declaratively adding event
         | handlers, setting properties, and dealing with lists.
         | 
         | [1]: https://lit.dev/docs/libraries/standalone-templates/
        
       | edflsafoiewq wrote:
       | It appears to be exactly the kind of manual-update code that
       | reactive view libraries exist to replace.
        
         | kyleee wrote:
         | It's probably about time for that to become fashionable again
        
           | ChocolateGod wrote:
           | IIRC its what frameworks like Svelte do when they hit the
           | compiler and optimize, which makes the best of both worlds.
        
             | wruza wrote:
             | They still nail "state" to element trees, which creates
             | unbenchmarkable but real update costs. Svelte does better
             | than react, but only within the same paradigm.
        
               | MrJohz wrote:
               | Can you describe what you mean by that a bit more? As I
               | understand it, with the new signals-based system in
               | Svelte, updating data directly updates the DOM.
        
               | division_by_0 wrote:
               | It's also worth noting that the Svelte signals
               | implementation is quite performant. [0]
               | 
               | [0] https://github.com/sveltejs/svelte/discussions/13277
        
           | JoeyJoJoJr wrote:
           | Do you mean subscribing to events/callbacks, manually
           | managing object lifecycle, manually inserting list elements,
           | keeping it in sync with the state, etc, etc. Because that was
           | all friggen horrible. Maybe new approaches could make it less
           | horrible, but there is no way I'd go back to what it was like
           | before React. If anything, I want everything to be more
           | reactive, more like immediate mode rendering.
        
           | vendiddy wrote:
           | It's easy to forget how tedious things used to be before
           | React became popular.
           | 
           | Keeping data in sync with the UI was a huge mental burden
           | even with relatively simple UIs. I have no desire to go back
           | to that.
        
         | _heimdall wrote:
         | Reactive view libraries exist to hide those details. I think
         | the OP is proposing that the benefit of reactive views/state
         | isn't worth the cost and complexity.
        
           | d357r0y3r wrote:
           | It is absolutely worth the cost and complexity. The cost and
           | complexity of building a web application using some home
           | grown vanilla JS system will end up being a horrible
           | engineering decision most of the time.
           | 
           | There have been zero times in my career where I thought "hmm,
           | maybe we shouldn't have build this thing in React and let's
           | just go back to page scripts." If you're building landing
           | pages and websites, then okay. But that's not most of what
           | we're all hired to build these days.
        
       | dullcrisp wrote:
       | Why not use Web Components? Is it because they're classes?
        
         | brigandish wrote:
         | I think it's because that repo is from 7 years ago, when
         | browser support[1][2] for components wasn't as widespread or
         | comprehensive.
         | 
         | [1] See the history section of
         | https://en.m.wikipedia.org/wiki/Web_Components
         | 
         | [2] https://caniuse.com/?search=web%20components
        
         | azangru wrote:
         | > Why not use Web Components?
         | 
         | If you check out his examples (e.g. clock), you will notice
         | that he is using web components.
        
       | athrowaway3z wrote:
       | This might be heresy to many JS devs, but I think 'state'
       | variables are an anti-pattern.
       | 
       | I use webcomponents and instead of adding state variables for
       | 'flat' variable types I use the DOM element
       | value/textContent/checked/etc as the only source of truth, adding
       | setters and getters as required.
       | 
       | So instead of:                 /* State variables */       let
       | name;            /* DOM update functions */       function
       | setNameNode(value) {         nameNode.textContent = value;
       | }            /* State update functions */       function
       | setName(value) {         if(name !== value) {           name =
       | value;           setNameNode(value);         }       }
       | 
       | it would just be akin to:                 set name(name) {
       | this.nameNode.textContent = name }       get name() { return
       | this.nameNode.textContent}            /* or if the variable is
       | used less than 3 times don't even add the set/get */
       | setState({name}){
       | this.querySelector('#name').textContent = name;       }
       | 
       | Its hard to describe in a short comment, but a lot of things go
       | right naturally with very few lines of code.
       | 
       | I've seen the history of this creating spaghetti, but now with
       | WebComponents there is separation of objects + the adjacent HTML
       | template, creating a granularity that its fusilli or macaroni.
        
         | fendy3002 wrote:
         | this is what I did in jquery era and it works very well, since
         | it seldom to have state management at that era. Sure there's
         | data binding libs like backbonejs and knockoutjs for a more
         | complex app, but this approach works well anyway.
         | 
         | Having a manual state that do not automatically sync to
         | elements will only introduce an unnecessary complexity later
         | on. Which is why libraries like react and vue works well, they
         | automatically handle the sync of state to elements.
        
         | Galanwe wrote:
         | I don't think that is heresy, essentially you are describing
         | what MUI calls unmanaged components - if I understand you well.
         | 
         | These have their places, but I don't see them as an either-or
         | replacement for managed components with associated states.
        
         | triyambakam wrote:
         | I really appreciate the concision and directness
        
         | mcintyre1994 wrote:
         | I think this makes a lot of sense when you're just wanting to
         | update a single DOM node. And if you wanted to eg update its
         | color as well, scoped CSS with a selector based on checked
         | state is probably as nice as anything else. But how does this
         | look if you want to pass that value down to child elements?
         | 
         | Eg if you had child form fields that should be enabled/disabled
         | based on this, and maybe they're dynamically added so you can't
         | hardcode it in this parent form field. Can you pass that get
         | function down the tree the same way you would pass react state
         | as a prop?
        
         | preommr wrote:
         | I think this is a very popular opinion.
         | 
         | A lot of people just wanted slight improvements like composable
         | html files, and a handful of widgets that have a similar api.
         | And for a long time it just wasn't worth the hassle to do
         | anything other than react-create-app even if it pulled in 100x
         | more than what people needed or even wanted.
         | 
         | But stuff has gotten a lot better, es6 has much better, web-
         | components... are there, css doesn't require less/sass. It's
         | pretty reasonable to just have a site with just vanilla tech.
         | It's part of why htmx is as popular as it is.
        
         | socalgal2 wrote:
         | I feel you, but isn't the state of truth for most websites
         | supposed to be whatever is in the database? The example TODO
         | List app, each TODO item has stuff in it. That's the source of
         | truth and I believe what is trying to be solved for for most
         | frameworks. In your example, where does name come from
         | originally? Let's assume it's a contact app. If the user picks
         | a different contact, what updates the name, etc...
         | 
         | If the user can display 2 contacts at once, etc...
        
         | Etheryte wrote:
         | This is the exact same thing, but with the state pushed one
         | level deeper, unless I misunderstand something here?
        
         | motorest wrote:
         | > (...) I think 'state' variables are an anti-pattern. I use
         | webcomponents (...)
         | 
         | It's unclear what you mean by "state variables". The
         | alternative to state variables you're proposing with
         | webcomponents are essentially component-specific state
         | variables, but you're restricting their application to only
         | cover component state instead of application state, and
         | needlessly restricts implementations by making webcomponents
         | mandatory.
         | 
         | > (...) but now with WebComponents there is separation of (...)
         | 
         | The separation was always there for those who wanted the
         | separation. WebComponents in this regard change nothing. At
         | most, WebComponents add first-class support for a basic
         | technique that's supported my mainstream JavaScript frameworks.
        
           | _heimdall wrote:
           | "State variables" is a section in the original article. It
           | shows a variable in the view, "name", that holds the value
           | separate from the DOM.
           | 
           | setName(value) first checks the local state variable, and if
           | different the value is both written to the state variable
           | _and_ the DOM.
           | 
           | The GP's pattern uses getters and setters to directly read
           | and write to the DOM, skipping the need for a local variable
           | entirely.
        
         | kikimora wrote:
         | This approach is simple but does not scale. People did this
         | long time ago, perhaps starting with SmallTalk in 80's and
         | VB/Delphi in 90's.
         | 
         | You need separation between components and data. For example
         | you got a list of 1000 objects, each having 50 fields. You
         | display 100 of them in a list at a time. Then you have a form
         | to view the record and another to update it. You may also have
         | some limited inline editing inside the list itself. Without
         | model it will be hard to coordinate all pieces together and
         | avoid code duplication.
        
           | epolanski wrote:
           | That screams pub sub which is trivial with JavaScript proxy
           | imho.
        
           | austin-cheney wrote:
           | So, state is simple, stupid simple.
           | 
           | The way to keep it simple is to have a single state object,
           | which is the one place where state is organized and accessed.
           | 
           | The way to make it scale is _architecture_. Architecture is a
           | fancy word that means a repeatable pattern of instances where
           | each instance of a thing represents a predefined structure.
           | Those predefined structures can then optionally scale
           | independently of the parent structure with an internal
           | architecture, but the utility of the structure's definitions
           | matter more.
           | 
           | Boom, that's it. Simple. I have written an OS GUI like this
           | for the browser, in TypeScript, that scaled easily until all
           | system memory is consumed.
        
             | alternatex wrote:
             | I feel like you completely misinterpreted their comment.
             | They replied to a comment saying that state should not be
             | centralized. They said that if the state decentralized (as
             | in held by individual child components) it's difficult to
             | coordinate between sibling and parent/child components.
             | 
             | It seems like you're saying that it's easy to do UI with a
             | centralized state, therefore agreeing with them whilst
             | having the tone of disagreement.
        
               | austin-cheney wrote:
               | The parent comment said: _This approach is simple but
               | does not scale_ and _You need separation between
               | components and data._ That is garbage and is what I
               | replied to.
               | 
               | I completely understand why JavaScript developers would
               | fail to read this as such as most JavaScript developers
               | are wholly incapable of programming, a forest for the
               | trees problem.
        
           | homarp wrote:
           | can you elaborate on the 'don't scale part'? because apps in
           | 90's don't see 'smaller' than webapps now
        
         | mondrian wrote:
         | The problem the name will have to be updated in 6 places in the
         | UI.
        
           | exe34 wrote:
           | what are those 6 places? how were they updated before?
        
             | mondrian wrote:
             | You have a card with a name, when you click it a detail
             | popup opens and the name is in there. There's also a delete
             | button saying "Delete <name>", which pops up a confirm
             | dialog and the name is in there, too. When you update the
             | name you have to update all those places.
             | 
             | The alternative is there's a canonical name variable, and
             | it's rendered in all those places. To update the name, you
             | just update that variable and "re-render", and those places
             | naturally pick up the new value.
        
               | exe34 wrote:
               | yeah okay, I agree.
        
           | bk496 wrote:
           | 1. Then add the 6 updates to the "setter" function 2. What UI
           | has the same data presented 6 times? Seems unnecessary
        
             | MrJohz wrote:
             | A lot of UIs have redundant data, it's very common to have
             | the same data represented in different ways. Consider the
             | comments page on HN, for example, which has plenty of
             | duplicate information:
             | 
             | The list of comments on a submission tells you how many
             | comments exist, but the comment count is also made explicit
             | at the top of the page directly underneath the submission
             | title.
             | 
             | If one person comments multiple times, their user name will
             | appear multiple times on the page, despite being the same
             | every time.
             | 
             | All the timestamps are presented as relative timestamps,
             | which means they're all dependent on the current time.
             | 
             | Now this is a very simple page, and it's not so important
             | that everything be updated live. But if it were, you'd need
             | to update every single timestamp on the page, keep all of
             | the usernames in sync in case a user changed their name,
             | insert new comments while also updating the comment count,
             | etc. There is a lot of redundancy in most UIs.
             | 
             | In fact, I vaguely remember one of the early React blog
             | posts using a very similar example (I think something to do
             | with Messenger?) to explain the benefits of having a data-
             | driven framework rather than using the DOM as the source of
             | truth for data. For a messaging application, it's much more
             | important that everything be live, and that elements don't
             | end up out-of-sync.
        
               | wordofx wrote:
               | HN is static data. It doesn't update. Re-rendering a user
               | name 20 times doesn't matter.
        
               | MrJohz wrote:
               | I wasn't trying to suggest that HN needs to update the UI
               | like this, just give an example of how even a relatively
               | simple UI like the one we're using now is full of
               | duplicate data. I then also gave the example of a
               | messaging app because that's a case where you do want
               | everything in sync all the time.
               | 
               | If you just rerender everything every time, then it's no
               | problem to keep the whole UI in sync. But you probably
               | don't want to render everything all the time - that's
               | unnecessary work, and will break any stateful elements in
               | the UI (such as form inputs that will get reset with
               | every render). That's where the idea of React comes from:
               | write code as if the whole UI is being rerendered every
               | time, but internally only rerender the parts of the UI
               | that have changed.
               | 
               | Now that has its own disadvantages, and I think there are
               | similar approaches out there, but the point is that
               | keeping UIs in sync is a surprisingly hard problem.
        
         | bk496 wrote:
         | +1 have had multiple bugs arise because the state in the
         | variable was not the same as the UI / DOM. Haven't had any
         | problems a pattern similar to yours.
         | 
         | If you have the edge case of lots of update (assignments to
         | .name) then just wrap the `.name = ...` in a leading debounce.
        
         | SkiFire13 wrote:
         | > use the DOM element value/textContent/checked/etc as the only
         | source of truth
         | 
         | How do you manage redundant state? For example a list with a
         | "select all" button, then the state "all selected"/"some
         | selected"/"none selected" would be duplicated between the
         | "select all" button and the list of elements to select.
         | 
         | This is the fundamental (hard) problem that state management
         | needs to solve, and your proposal (along with the one in the
         | OP) just pretends the issue doesn't exist and everything is
         | easy.
        
           | liveafterlove wrote:
           | That is just select with multi. And one can also have class
           | vs id.
        
           | jdsleppy wrote:
           | They could always fall back to storing a value in a hidden
           | element in the worst case. All/some/none selected is often
           | done with an indeterminate state checkbox
           | https://developer.mozilla.org/en-
           | US/docs/Web/HTML/Reference/... that can represent all three
           | states.
           | 
           | Maybe I don't understand the problem you are talking about.
        
             | robocat wrote:
             | As soon as you need to store some state elsewhere you can
             | store it in another suitable form (there's often some state
             | not visually represented). I seem to recall jQuery stored
             | state on a POJO (plain old JavaScript object) within a
             | JavaScript variable of an element.
        
           | SvenL wrote:
           | A List of items could just contain checkboxes holding the
           | state of selected/not selected. Then it's a trivial query
           | selector. To get every selected item or to know if every item
           | is selected.
        
           | johnisgood wrote:
           | Did you know you can have "stateful" UI without any
           | JavaScript, using pure CSS and HTML? JS-less (Tor) websites
           | use them.
           | 
           | I have implemented a fully functional, multi-state CAPTCHA
           | using only HTML + CSS for state simulation, and PHP for real
           | validation.
        
         | starwatch wrote:
         | I love the idea of a single source of truth. However, how does
         | your approach handle browsers / plugins that modify the dom?
         | For example, I can imagine Google Translate altering the
         | textContent and some resulting downstream effects on business
         | logic.
        
           | _heimdall wrote:
           | If the view needs to react to the updated DOM you could use a
           | custom element and the attribute changed callback. If you
           | don't need to react to if the updated text content would just
           | be there the next time the view needs to read it.
        
         | _heimdall wrote:
         | This was my first thought as well. I like the convention the OP
         | is proposing here, with this one tweak making the DOM the
         | single source of truth rather than local state inside views or
         | components.
         | 
         | Hell, even in react I try to follow a similar pattern as much
         | as possible. I'll avoid hooks and local state as much as
         | possible, using react like the early days where I pass in
         | props, listen to events, and render DOM.
        
         | chrismorgan wrote:
         | A couple of issues that will arise from this:
         | 
         | * Using DOM attribute or text nodes limits you to text only.
         | This is, in practice, a _very_ big limitation. The simple cases
         | are Plain Old Data which can be converted losslessly at just an
         | efficiency cost, like HTMLProgressElement.prototype.value,
         | which converts to number. Somewhat more complex are things like
         | classList and relList, each a live DOMTokenList mapping to a
         | single attribute, which needs unique and persistent identity,
         | so you have to cache an object. And it definitely gets more
         | intractable from there as you add more of your own code.
         | 
         | * Some pieces of state that you may care about aren't stored in
         | DOM nodes. The most obvious example is
         | HTMLInputElement.prototype.value, which does _not_ reflect the
         | value attribute. But there are many other things like scroll
         | position, element focus and the indeterminate flag on
         | checkboxes.
         | 
         | * Some browser extensions will mess with your DOM, and there's
         | nothing you can do about it. For example, what you thought was
         | a text node may get an entire element injected into it, for ads
         | or dictionary lookup or whatever. It's hard to write robust
         | code under such conditions, but if you're relying on _your_ DOM
         | as your source of truth, you _will_ be disappointed
         | occasionally. In similar fashion, prevailing advice now is not
         | to assume you own all the children of the  <body> element, but
         | to render everything into a div inside that body, because too
         | many extensions have done terrible things that they should
         | never have done in the first place.
         | 
         | It's a nice theory, but I don't tend to find it scaling very
         | well, applied as purely as possible.
         | 
         | Now if you're willing to relax it to adding your own
         | _properties_ to the DOM element (as distinct from attributes),
         | and only reflecting to attributes or text when feasible, you
         | can often get a lot further. But you may also find frustration
         | when your stuff goes awry, e.g. when something moves a node in
         | the wrong way and all your properties disappear because it
         | cloned the node for some reason.
        
         | rs186 wrote:
         | > I use the DOM element value/textContent/checked/etc as the
         | only source of truth
         | 
         | Interesting idea but breaks down immediately in any somewhat
         | serious application of reasonable size. e.g. i18n
        
         | MatthewPhillips wrote:
         | Hey, I'm the author of this doc. The reason for the pattern is
         | to make it so you always can find why a mutation occured. So
         | combining state variables and dom changes is ok as long as
         | that's the only place that does the mutation. If not, now
         | you've made it harder to debug. I keep the strict separation so
         | that I can always stick a debugger and see a stack trace of
         | what happened.
        
       | triyambakam wrote:
       | I like to prompt Claude to create artifacts in plain HTML, CSS
       | and JS. I like the portability and hackability of these. React is
       | too heavy for a lot of simple ideas even if reactivity is needed.
        
       | dsego wrote:
       | This reminds me of the venerable backbone js library.
       | https://backbonejs.org/#View
       | 
       | There is also a github repo that has examples of MVC patterns
       | adapted to the web platform.
       | https://github.com/madhadron/mvc_for_the_web
        
       | yumaikas wrote:
       | I've been working on https://deja-vu.junglecoder.com which is an
       | attempt to build a JS toolkit for HTML-based doodads that shares
       | some ideas with this.
       | 
       | I don't quite have proper reactive/two-way data binds worked out,
       | but grab/patch seem pretty nice as these things go. Also, the way
       | this uses templates makes it very easy to move parts of the
       | template around.
       | 
       | It's also largely injection safe because it's using innerText or
       | value unless told otherwise.
        
       | popcorncowboy wrote:
       | ...eschews abstractions...
        
       | admiralrohan wrote:
       | Might be feasible with the advent of vibe coding. For frontend
       | heavy applications can choose this route for performance reasons.
       | Starred, will follow the project.
        
       | atoav wrote:
       | I program for roughly two decades now and I never got warm with
       | frontend frameworks. Maybe I am just a backend guy, but that
       | can't be it since I am better in vanilla JS, CSS and HTML than
       | most frontend people I have ever met.
       | 
       | I just never understood why the overhead of those frameworks was
       | worth it. Maybe that is because I am so strong with backends that
       | I think most security-relevant interactions have to go through
       | the server anyways, so I see JS more as something that adds
       | clientside features to what should be a solid HTML- and CSS-
       | base..
       | 
       | This kind of guide is probably what I should look at to get it
       | from first principles.
        
         | edflsafoiewq wrote:
         | The basic problem is when some piece of state changes, all the
         | UI that depends on that state needs to be updated. The simple
         | solution presented in the link is to write update functions
         | that do the correct update for everything, but as the
         | dependency graph becomes large and keeps changing during
         | development, these becomes very hard to maintain or even check
         | for correctness. Also the amount of code grows with the number
         | of possible updates.
         | 
         | Reactive view libraries basically generate the updates for you
         | (either from VDOM diffing, or observables/dependency tracking).
         | This removes the entire problem of incorrect update functions
         | and the code size for updates is now constant (just the size of
         | the library).
        
           | skydhash wrote:
           | But what if your dependency graph never becomes large (HN,
           | Craiglist,...)?
           | 
           | I believe a lot of web applications can go without any
           | reactive framework as using one is a slippery slope. You
           | start with React and 80% of your code is replacing browser
           | features. Imperative may not be as elegant, but it simpler
           | when you don't need that much extra interactivity.
        
             | edflsafoiewq wrote:
             | Then you don't need it. Same for if you can do everything
             | (or most everything) with page reloads, or if you don't
             | have any reactivity at all. But the problem is still real,
             | even if people use frameworks when they don't really have
             | to.
        
       | seumars wrote:
       | It seems the "hard way" here is just avoiding frameworks. The
       | real hard part of UI is in fact state management and the myriad
       | of methods for handling state.
        
       | smarkov wrote:
       | People like to hate on PHP, but PHP provides you with all the
       | tools you need to write a fully working backend, where as JS
       | provides you with half-assed solutions for writing frontend,
       | which is why we have 1000 frameworks and we still can't agree on
       | how to write frontend code. Seriously, we don't even have a
       | convention for writing a simple reusable component with vanilla
       | JS, everyone makes up their own thing. Web components were
       | supposed to be that, but they're a good example of what I meant
       | by "half-assed", because they're ugly, verbose, clunky, don't
       | really solve the right problems, and nobody likes writing them.
        
         | pjmlp wrote:
         | That is why I made my peace with Next.js.
         | 
         | It is the only framework that feels like I am using JSP, JSF,
         | ASP.NET, Spring, Quarkus, PHP.
         | 
         | Don't plan to use anything else in JS space, unless by external
         | decisions not under my control.
        
           | girvo wrote:
           | While I chafe at some of its decisions, you're still correct.
           | It's the only thing really in that space that's fully
           | featured enough.
        
         | brulard wrote:
         | I don't think PHP is any better in solving the backend, than JS
         | is in solving frontend. On the Frontend the situation is not
         | ideal, but we made big leaps every let's say 5 years, going
         | from jQuery to React and from React to later generation
         | frameworks like svelte / solid etc. Yes, the landscape is
         | fragmented and there are maybe too many options, but you make
         | it sound like PHP is universally used as the backend solution,
         | while I see it being used little these days except for legacy
         | systems from 15-20 years ago.
        
           | smarkov wrote:
           | I never said that PHP was universally used, just that it has
           | answers to most problems.
           | 
           | jQuery has become obsolete these days because the problems it
           | solves have largely been solved by additions to JS, but the
           | interactivity of websites has continued to increase and
           | browsers have yet to catch up to that. Frameworks like React
           | actively fight against the browser rather than work with it
           | by maintaining its own DOM state and constantly creating
           | copies of state for every re-render of a component, along
           | with a bunch of other magic. That's a lot of unnecessary
           | loopholes just to make up for JS's lack of features when it
           | comes to writing reactive UI.
        
           | hu3 wrote:
           | > you make it sound like PHP is universally used as the
           | backend solution, while I see it being used little these days
           | except for legacy systems from 15-20 years ago.
           | 
           | Your eyes deceive you.
           | 
           | https://finance.yahoo.com/news/exclusive-laravel-
           | raises-57-m...
        
       | wruza wrote:
       | Something is wrong with web developers culture, cause even in
       | framework-free vanilla mode they cannot get rid of data
       | localization and welding the data and "component" trees together
       | irrepairably.
       | 
       | Rather than building a querySelector-able tree of elements to and
       | monkey-patching mutiplexing nodes for syncing element counts, you
       | invent the most bizarre ways to chain yourselves to the wall. For
       | long time I couldn't understand what exactly drives this almost
       | traumatic habit, and it's still a mystery.
       | 
       | For the interested, this is the outline I count as non-bizarre:
       | 
       | - make an html that draws your "form" with no values, but has
       | ids/classes at the correct places
       | 
       | - singular updates are trivial with querySelector; write a few
       | generic setters for strings, numbers, dates, visibility,
       | disability, e.g. setDate(sel, date)
       | 
       | - sync array counts through cloning a child-template, which is
       | d-hidden and locatable inside a querySelector-able container;
       | make syncArray(array, parentSel, childSel) function
       | 
       | - fill new and update existing children through "<parent> :nth-
       | child(n) <name>"
       | 
       | - update when your data changes
       | 
       | Data can change arbitrarily, doesn't require passing back and
       | forth in any form. All you have to do is to update parts of your
       | element tree based on your projections about affected areas.
       | 
       | And no, your forms are not so complex that you cannot track your
       | changes or at least create functions that do the mass-ish work
       | _and_ update ui, so you don 't have to. For all the forms you've
       | done, the amount of work needed to ensure that updates are
       | performed is amortized-comparable with all the learning cliffs
       | you had to climb to turn updates into "automatic". Which itself
       | is a lie basically, cause you still have to jump through hoops
       | and know the pitfalls. The only difference is that rather than
       | calling you inattentive, they now can call you stupid, cause you
       | can't tell which useCrap section your code should go to.
        
       | epolanski wrote:
       | I have been writing recently an application in plain "vanilla"
       | TypeScript with vite, no rendering libraries, just old-style DOM
       | manipulation and I have to say I more and more question front end
       | "best" practices.
       | 
       | I can't conclude it scales, whatever it means, but I can conclude
       | that there are huge benefits performance-wise, it's fun, teaches
       | you a lot, debugging is simple, understanding the architecture is
       | trivial, you don't need a PhD into "insert this
       | rendering/memoization/etc" technique.
       | 
       | Templating is the thing I miss most, I'm writing a small vite
       | plugin to handle it.
        
         | klysm wrote:
         | The problems with this approach are exacerbated in a team
         | setting. The architecture might be trivial from your
         | perspective but good luck getting a bunch of other folks on
         | board with different mental models and levels of experience.
        
         | klysm wrote:
         | If you look at it as a tradeoff space it makes more sense why
         | the majority of folks are on some kind of react. What kind of
         | problems do you want to experience and have to solve in a
         | production setting?
        
         | iamsaitam wrote:
         | "I can also ditch a database and just dump everything into a
         | text file." <- This is what you're saying. It isn't hard to see
         | the problem with this kind of thing.
        
           | AmalgatedAmoeba wrote:
           | ngl, a lot of the times, an in-memory "database" that gets
           | backed up to a file is perfectly reasonable. Even consumer
           | devices have dozens of gigabytes of RAM. What percentile of
           | applications needs more?
           | 
           | Just because a technology works well for a few cases
           | shouldn't mean it's the default. What's the 80% solution is
           | much more interesting IMO.
        
             | skydhash wrote:
             | > _an in-memory "database" that gets backed up to a file is
             | perfectly reasonable._
             | 
             | We have org-mode, application configs, and music playlists
             | as three widely used examples for this.
             | 
             | You switch to a database when you need to query and update
             | specific subsets of the data, and there's the whole
             | concurrency things when you have multiple applications.
        
         | prisenco wrote:
         | I take it a step further and go no-build js with jsdoc.
         | 
         | The hardest part about scaling this approach is finding UX
         | designers who understand the web. Just as frontend devs have
         | trained themselves to "think in react" over the past decade, so
         | have designers. The understanding of the underlying
         | capabilities and philosophies of the web have been lost to the
         | idea that the web and mobile can be effectively the same thing.
         | 
         | This approach can go far if the team using it knows and respect
         | web technology.
        
           | designerarvid wrote:
           | IMO designers should read hypermedia systems by the htmx guy.
        
         | nonethewiser wrote:
         | Can you elaborate on website functionality, team size, and
         | production readiness?
         | 
         | I mean I totally agree on small personal projects. Thats just
         | never the limiting factor though.
        
         | only-one1701 wrote:
         | I get what you're saying but people still write SPAs
        
       | jbverschoor wrote:
       | You had me at JavaScript
        
       | efortis wrote:
       | I use a helper similar to React.createElement.
       | const state = { count: 0 }            const init = () =>
       | document.body.replaceChildren(App())       init()
       | function App() {         return (           h('div', null,
       | h('output', null,  `Counter: ${state.count}`),
       | h(IncrementButton, { incrementBy: 2 })))       }
       | function IncrementButton({ incrementBy }) {         return (
       | h('button', {             className: 'IncrementButton',
       | onClick() {                state.count += incrementBy
       | init()             }           }, 'Increment'))       }
       | function h(elem, props = null, ...children) {         if (typeof
       | elem === 'function')           return elem(props)
       | const node = document.createElement(elem)         if (props)
       | for (const [key, value] of Object.entries(props))             if
       | (key === 'ref')               value.current = node         else
       | if (key.startsWith('on'))
       | node.addEventListener(key.replace(/^on/, '').toLowerCase(),
       | value)         else if (key === 'style')
       | Object.assign(node.style, value)         else if (key in node)
       | node[key] = value         else           node.setAttribute(key,
       | value)         node.append(...children.flat().filter(Boolean))
       | return node       }
       | 
       | Working example of a dashboard for a mock server:
       | https://github.com/ericfortis/mockaton/blob/main/src/Dashboa...
        
         | simonw wrote:
         | That looks like it replaces the entire document every time
         | state changes. How's the performance of that?
        
           | WickyNilliams wrote:
           | Even if performance is fine, the big usability issue is that
           | it will blow away focus, cursor position etc every render.
           | Gets very painful for keyboard use, and of course is a fatal
           | accessibility flaw
        
             | efortis wrote:
             | yes, that's the downside, focus is lost on init()
        
               | WickyNilliams wrote:
               | Really wish the browser gave us better APIs for updating
               | the DOM. Creation is extremely easy, but after that you
               | either have to invent some reconciliation system or stick
               | with imperative updates
        
       | floydnoel wrote:
       | this is pretty much how i wrote one of my side projects,
       | https://bongo.to
       | 
       | it was fun and very fast to ship. no frameworks or libraries
       | needed.
        
       ___________________________________________________________________
       (page generated 2025-04-19 23:01 UTC)