7 ms·
Show HN: A JavaScript UI library for imperative JSX
I've been building a web UI library for a side project of mine. I thought it might be useful to others, so I'm releasing it as open source.
To put it simply, I realized that most of my pain points with React come from its declarative model ui=f(state). So I'm trying something that I'm calling "imperative JSX." Instead of treating JSX as the source of truth for your UI, it essentially becomes a query interface for DOM manipulation.
I first had the idea for it a few months ago, and only began writing it in earnest last week, so it's extremely early and nowhere near production-ready. Still, I'd appreciate feedback on it (positive and negative)!
The side project I'm working on is called Matry, so the library is currently called @matry/dom. I'm slowly building up a list of examples of it in action at this repo: https://github.com/matry/dom-recipes https://github.com/matry/dom-recipes
Cheers!
- lgas 2y agoCan you talk a little about the pain points you encountered, which this presumably solves?
- danielvaughn 2y agoSure! There are several, but let's take a trivial one - setTimeout. This is an example of something that is inherently imperative, and therefore requires some awkward logic to get it working correctly in React. Here's an article explaining how to do it: https://codedamn.com/news/reactjs/how-to-use-settimeout-in-react-complete-guide-with-examples https://codedamn.com/news/reactjs/how-to-use-settimeout-in-r... In contrast, here's how you would do it in matry: let someValue = 0; setTimeout(() => { someValue++; setContent(<p id="some-element">value is {someValue}</p>) }, 1000) There's no special concept you need to understand, and what's more is that it will not perform any rendering logic on #some-element's siblings or descendants. React may do that depending on how you structured your component.
- SebastianKra 2y agoThis is IMO a great example for React and declarative programming. > There's no special concept to understand By not understanding that you need to cleanup timers when your view unmounts, you've introduced a subtle bug that your coworker will discover in 6 months when users report that the counter sometimes increases twice as fast. I've worked on Angular-like apps before, and storing and cleaning up timers was always a pain point: private timer private someValue = 0 onMount() { timer = setTimeout(() => { someValue++; setContent(<p id="some-element">value is {someValue}</p>) }, 1000) } onUnmount() { clearTimeout(timer) } React on the other hand, clearly defines the concept of an effect as something that is initiated after the component renders and then cleaned up. You also keep the setup and cleanup close together, so you don't need to manually store the timer handle. const [someValue, setSomeValue] = useState(0) useEffect(() => { const timeout = setTimeout(() => { setSomeValue(v => v + 1) }, 1000) return () => clearTimeout(timeout) }, []) return <p>value is {someValue}</p> People often criticise useEffect for being hard to grasp, but it is the perfect essence of how to manage, well... effects in a component-based system. That's why Vue and Svelte have the same thing: https://vuejs.org/guide/essentials/watchers.html https://vuejs.org/guide/essentials/watchers.html https://svelte.dev/blog/runes https://svelte.dev/blog/runes
- Spivak 2y ago> By not understanding that you need to cleanup timers when your view unmounts I think this library eschews the entire concept of mounting so that point is kind of moot. Will you likely re-invent it again in any non-trivial app, sure, but when you're writing imperative JS it's probably a MPA where code runs top to bottom. Clean up will happen when everything gets thrown away on navigation.
- danielvaughn 2y agoYep, and if you are writing an SPA where cleanup is an issue, personally I think a web component would be a better fit. It's native to the platform and offers lifecycle hooks out of the box. I'm currently playing around with how my library might be used for web components.
- 2y ago
- gushogg-blake 2y agoHmm, interesting. What advantages do you see of using JSX for querying over the old jQuery-style $("#id").append(...)? Did you consider doing something more like that, but allowing JSX instead of HTML strings like it was done in jQuery?
- danielvaughn 2y agoUsing JSX syntax allows you to consolidate all similar operations in a single call. matry-dom traverses the JSX you pass into a call, finds all leaf nodes, and performs the operation on each of them. So you could do this: function onUserLogin(user) { matry.replace( <main id="root"> <img key="user-profile" src={user.profileImage} /> <aside> Welcome, {user.name} </aside> </main> ) } In this example, the img and aside elements could be anywhere in the tree.
- udbhavs 2y ago> If we accept the fact that the web is inherently imperative, then I believe we can resolve most if not all of the above problems. We don't need to throw the baby out with the bathwater though, because JSX is fantastic. Also look into SolidJS for dropping the "functional" component model while still retaining JSX - it looks similar to React but works more like Vue, running components as setup functions only on initial render and doing state updates with mutations via signals.
- danielvaughn 2y agoYeah I like the idea of signals a lot. I haven't played with SolidJS but I'll give it a shot.
- mati365 2y agoThat's the worst idea for framework I've seen recently
- danielvaughn 2y agoSeems like others are downvoting you, but I appreciate seeing the sentiment. I’ll just note that the some of the most influential technologies I’ve encountered in my career were initially disgusting to me. I thought React was gross and I thought Tailwind was gross. But now I love them. Doesn’t mean this is going to be good, just that first impressions aren’t everything.
- lgas 2y agoTailwind is gross. It's just that it's the least gross of the available options.
- zarathustreal 2y agoWhile I do agree, it would be nice to see something more than just “I hate this” in a comment. For example you could remind the author of the perils of imperative programming such as needing to mentally simulate application state at every line to understand the program vs functional programming where you have referential transparency
- danielvaughn 2y agoYep fair, although imperative and functional aren’t exactly mutually exclusive. The examples I have right now are really just for demonstration purposes, but you could use this library in a ton of different ways. Right now I’m building some demos of it using mobx, for example.
- darepublic 2y agoReact, the good parts
- danielvaughn 2y agoYep, although I do really like the declarative model. I don’t think anyone wants to go back to jQuery. But over time, the downstream effects of React are starting to show up. There’s significant complexity with adopting React in an SSR context, and a bunch of bespoke concepts and weird little rules are required to make it work.
- justin_murray 2y agoIt's cool to see a novel idea like this get prototyped and then opened up for discussion. As others have hinted at, this feels like jQuery with different syntax and more limitations. In its current form, I can't imagine choosing this over jQuery (in part because I already know jQuery, and in part because the limited reach of your selectors feels like it would be a roadblock very quickly). Perhaps you could reshape this to be an extension of jQuery, where this alternative jsx syntax is available if you want it, but the full power of jQuery's selectors is also available (without having to mix two libraries with overlapping purpose)? That aside, I can't imagine abandoning React for this. That would feel like a step backwards to me. IMO, the most valuable thing React brought to the ecosystem is a simple way to make reusable components. Much of React's convenience is because of its functional nature: each component instance has an isolated scope/context, making it easy to reason over the behavior of a small widget without worrying about external effects from the larger application. From what I can see in your examples, you're relying on things like global identifiers/keys, which I think leads to a mess in a large application. Take your counter example: what if my application needs 5 such counters? Can I do this without significant code duplication? I could probably wrap it in a function, but I'd likely need to pass in some unique id to distinguish between the 5 usages. This is a huge part of what React does for you, just keeping track of which component instances go where in the DOM (without needing DOM `id`s everywhere). It could be that I'm missing something. It'd be great to see some larger examples of a more complex application with widget reuse, to see how that feels.
- danielvaughn 2y agoYeah I opted for quick, simple demonstrations of the basic ideas. In a real application, at least one of any significant complexity, I wouldn't be writing the code you see in those demos. I'm experimenting with how it might integrate with state management libraries, currently with mobx. I haven't done any work on this front yet, but I'm especially excited to see how this might fit into an SSR/MPA app. I have some ideas though; my goal is to implement ISR/streaming/etc without the need for weird little conventions like "use server".
- justin_murray 2y agoJust a few more thoughts to add: When I see code like count++; setContent(<p key="counter">The count is {count}</p>); I immediately think about how someone, at some point, will introduce bugs/weirdness where they update some state (`count`), but then forget to make the `setContent` call to update the DOM. That's a very useful thing that React's `useState` does; a single way to update the state that is guaranteed to always be synced with the DOM. Of course, I could make a `setCount` function to help abstract this, but that's more boilerplate and just more opportunity for someone to screw it up. Another point related to scoped components: one thing that I saw happen in jQuery codebases (and I think the same would happen here) is that it's too easy to mistakenly write a rogue selector that affects some part of the DOM that is far away from what was intended. It's one of those great power = great responsibility things... but I think this power more often leads to complex and confusing code that is hard for someone to understand and maintain. React can be frustrating at times, when it seems like it would be easiest for a parent component to directly manipulate some nested child (or vice versa), but I think this friction often just leads to code that is easier to reason over.
- jauco 2y agoThis reminds me of google’s incremental dom (https://google.github.io/incremental-dom/ https://google.github.io/incremental-dom/ ) it surely made some things (like making sure an element has focus) a lot easier to do and in general felt a bit more like something that works with the browser instead of against it.
- danielvaughn 2y agoThat's super interesting, hadn't heard about that, thanks.
- peterleiser 2y agoJSX without React is also kind of fun: https://medium.com/@victor.dramba/can-you-do-jsx-without-react-88fb26b4e349 https://medium.com/@victor.dramba/can-you-do-jsx-without-rea... and it's working example at: https://github.com/dvictor/jsx-no-react/blob/master/index.tsx https://github.com/dvictor/jsx-no-react/blob/master/index.ts...
- danielvaughn 2y agoYeah this is essentially what I'm doing. Though I'm fighting with rollup in the process. Might drop down to ESBuild if I need it.
- i_like_robots 2y agoAgreed! About 5 or 6 years ago I wrote a library to demonstrate to my teams that JSX a) was not magic b) not limited to React. After that was done I became a little obsessed with making the library the fastest JSX renderer - which I think is still true: https://github.com/i-like-robots/hyperons https://github.com/i-like-robots/hyperons
- cnity 2y agoAdd me to this club of JSX hackers: https://crwi.uk/posts/hiccup/ https://crwi.uk/posts/hiccup/ I used this approach to make a syntax highlighted text editor in < 5kb of JS: https://crwi.uk/experiments/text-editor/ https://crwi.uk/experiments/text-editor/
- danielvaughn 2y agoThese are awesome (both parent comments). Love seeing that I'm not alone in my JSX curiosity.
- cnity 2y agoWith our powers combined we can reinvent the tiny fun internet.
- 2y ago
- llimllib 2y agoMithril seems like it can do something pretty similar: https://mithril.js.org/jsx.html https://mithril.js.org/jsx.html (Not trying to discourage you, just linking it)
- danielvaughn 2y agoNo this is great, thanks for sharing. I've had several people point me to things in this thread that I'd never seen before, and I appreciate it.
- breadwinner 2y agoIf you want to use JSX without React see this 500-line lib: https://github.com/wisercoder/uibuilder https://github.com/wisercoder/uibuilder And here's an app built using this lib: https://github.com/wisercoder/eureka/tree/master/webapp/ClientApp https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
- danielvaughn 2y agoThis is great, I'm gonna have to start making a list because this is like the 5th or 6th library I've been linked to in this thread. Love it, thanks for sharing.
- padjo 2y ago> The structure of your application state ends up being shaped by the UI, when it should be the other way around I don’t understand this premise at all. Are you saying your UI should be shaped by your application state? That doesn’t make any sense to me, surely the application state exists to implement a desired UI?
- danielvaughn 2y agoI could've said that better, what I really meant is that UI is (a) downstream of state, and (b) has a different structure than state. The UI is a tree but state is basically a graph, and it doesn't make sense to couple the two. This is the same position that SolidJS takes, for instance. To respond to your last statement though, I feel the opposite is true. UI's don't exist for their own sake, they only exist to support the application and its behavior.
- gedy 2y agoThanks for sharing, neat. This makes me curious about the psychology of declarative vs imperative, and why some people gravitate towards one or the other (at least with UI code). Back in the jQuery days, a lot of my hatred towards "JavaScript" was actually imperative UI code in hindsight. In the years since, I now enjoy declarative UI more but see some developers bang their head against declarative UI frameworks. It's an interesting contrast.
- danielvaughn 2y agoI also enjoy declarative UI. I hated imperatively building up the interface with jQuery, even with the helpers it just felt so tedious. This library is my attempt to have my cake and eat it too. Like I love being able to just write <div><p>hello world</p></div> in my javascript, it feels very nice. At the same time, with React I absolutely hate it when I do things that are inherently imperative. A prime example is the following: I have a list of items that can be named by the user, so each item's display name will be a text input. When the user clicks the "create new item" button, a new item is added to the list, and the text input of the new item is immediately focused as soon as it's added to the DOM. ^ doing this in React sucks.
- gedy 2y agoThe only thing that is awkward imho is the focusing, the rest is a really good fit for declarative, e.g. here's your list of "things", render a UI for each.
- spankalee 2y agoI would never want to write it the same element in multiple places like this. That's one of the main points of templating in the first place: to write the code for a piece of output once, with all of the bindings that can affect it. What happens here if you update the JSX for an element in one place and forget to do so in the others?
- danielvaughn 2y agoIt's true that the library doesn't stop you from shooting yourself in the foot by duplicating UI references. But you can limit the effect of this with a bit of design work. I'm currently iterating on a few ideas, but this lib is only a week old, so I'm still thinking my way through it.
- fbn79 2y agoWhy not use template literals and a css like syntax for selecting and update? setContent(css`p[key=counter]{ content: 'The count is ${count}'}; }`);
- danielvaughn 2y agoGreat question. So right now the library is using xpath expressions under the hood, which are more powerful than css selectors because they can query for content as well.
- wetpaws 2y ago[dead]
- crubier 2y agoThis is jQuery-like. Just syntax sugar above the imperative DOM API. Looks like the younger generation didn't know the pain of years of imperative UI "frameworks" and needs to learn this the painful way. Enjoy!
- danielvaughn 2y agoI wrote jQuery from 2009-2016
- crubier 2y agoAnd I can tell you liked it! It’s fine, but not all of us did!