7 ms·
JavaScript Views, the Hard Way – A Pattern for Writing UI
- dullcrisp 1y agoWhy not use Web Components? Is it because they’re classes?
- deleted 1y ago[deleted]
- brigandish 1y agoI 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 https://en.m.wikipedia.org/wiki/Web_Components [2] https://caniuse.com/?search=web%20components https://caniuse.com/?search=web%20components
- azangru 1y ago> Why not use Web Components? If you check out his examples (e.g. clock), you will notice that he is using web components.
- zffr 1y agoThe 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 1y agoConvention 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 1y agoAny 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 1y agoUsing a framework like react constrains developers in a different way. React isnt simply a convention like the linked example.
- _heimdall 1y agoI 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.
- simg 1y agoreact isn't a framework, it's a library.
- atum47 1y agoOn 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 https://github.com/victorqribeiro/TinyJS 2 - https://github.com/victorqribeiro/Chip8js/blob/master/js/ColorPicker.js https://github.com/victorqribeiro/Chip8js/blob/master/js/Col...
- npodbielski 1y agoI 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 1y agoIn 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 1y agoYou might want to look at something like morph Dom to keep input focus when you have to re-render the form, for example.
- lylejantzi3rd 1y agoI 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 1y agoI like it. Not only does it move the UI into JavaScript, but it moves the scripting into the HTML!
- Koffiepoeder 1y agoHave a feeling this will lead to XSS vulnerabilities though.
- MrJohz 1y agoHow 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 1y ago
- edflsafoiewq 1y agoIt appears to be exactly the kind of manual-update code that reactive view libraries exist to replace.
- kyleee 1y agoIt’s probably about time for that to become fashionable again
- ChocolateGod 1y agoIIRC its what frameworks like Svelte do when they hit the compiler and optimize, which makes the best of both worlds.
- wruza 1y agoThey 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 1y agoCan 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 1y agoIt's also worth noting that the Svelte signals implementation is quite performant. [0] [0] https://github.com/sveltejs/svelte/discussions/13277 https://github.com/sveltejs/svelte/discussions/13277
- JoeyJoJoJr 1y agoDo 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.
- athrowaway3z 1y agoThis 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 1y agothis 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 1y agoI 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 1y agoI really appreciate the concision and directness
- mcintyre1994 1y ago
- triyambakam 1y agoI 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 1y agoThis reminds me of the venerable backbone js library. https://backbonejs.org/#View 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 https://github.com/madhadron/mvc_for_the_web
- ChiperSoft 1y agoI would love to see a new take on backbone in the modern web without any jQuery integration. I genuinely miss how easy and powerful backbone views are.
- hyfgfh 1y ago[flagged]
- yumaikas 1y agoI've been working on https://deja-vu.junglecoder.com 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 1y ago...eschews abstractions...
- admiralrohan 1y agoMight 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 1y agoI 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 1y agoThe 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 1y agoBut 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 1y agoThen 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 1y agoIt 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 1y agoPeople 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 1y agoThat 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 1y agoWhile 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 1y agoI 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 1y agoI 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.
- wruza 1y agoSomething 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 1y agoI 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 1y agoThe 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 1y agoIf 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 1y ago"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.
- deleted 1y ago[deleted]
- AmalgatedAmoeba 1y agongl, 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.
- jbverschoor 1y agoYou had me at JavaScript
- deleted 1y ago[deleted]
- efortis 1y agoI 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/Dashboard.js https://github.com/ericfortis/mockaton/blob/main/src/Dashboa...
- simonw 1y agoThat looks like it replaces the entire document every time state changes. How's the performance of that?
- WickyNilliams 1y agoEven 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 1y agoyes, that’s the downside, focus is lost on init()
- 1y ago
- floydnoel 1y agothis is pretty much how i wrote one of my side projects, https://bongo.to https://bongo.to it was fun and very fast to ship. no frameworks or libraries needed.