JavaScript Views, the Hard Way – A Pattern for Writing UI

58 voat 29 4/19/2025, 2:10:52 AM github.com ↗

Comments (29)

athrowaway3z · 1h ago
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.

preommr · 9m ago
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.

mcintyre1994 · 47m ago
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?

lastdong · 9m ago
This. Webcomponents are now supported across browsers, and are The Hard Way.
fendy3002 · 1h ago
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 · 1h ago
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 · 1h ago
I really appreciate the concision and directness
yumaikas · 12m ago
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.

dsego · 38m ago
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

zffr · 3h ago
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 · 3h ago
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.

edflsafoiewq · 2h ago
It appears to be exactly the kind of manual-update code that reactive view libraries exist to replace.
kyleee · 2h ago
It’s probably about time for that to become fashionable again
lylejantzi3rd · 3h ago
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>
      `
    }
chrismorgan · 2h ago
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 · 1h ago
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 · 50m ago
Thus recreating Perl’s taint mode. Everything new is old.
lylejantzi3rd · 13m ago
Posts are sanitized on the server side. This is client side code.
MrJohz · 2h ago
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.
lylejantzi3rd · 10m ago
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.
dleeftink · 2h ago
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

econ · 2h ago
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.

hyperhello · 2h ago
I like it. Not only does it move the UI into JavaScript, but it moves the scripting into the HTML!
Koffiepoeder · 2h ago
Have a feeling this will lead to XSS vulnerabilities though.
atum47 · 3h ago
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...

triyambakam · 1h ago
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.
hyfgfh · 16m ago
Java script
dullcrisp · 2h ago
Why not use Web Components? Is it because they’re classes?
brigandish · 18m ago
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