Show HN: A JavaScript UI library for imperative JSX
npmjs.comI'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
Cheers!
It'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.
Just a few more thoughts to add:
When I see code like
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.
"greater power & greater responsibility" is definitely the tradeoff I'm making. One of the tradeoffs I list in the readme is that you really need to think about the UI in a way that you don't with React. It's certainly more expedient to use React and let it handle the platform itself. On the other hand, you pay for that in a multitude of other ways - it's more than just a few frustrations here and there.
I have to strongly agree with this. The entire point of react was to avoid all the challenges of old imperative coding of syncing up state with render state. The Dom digging was an absolutely genius idea to stop that nonsense and just render the correct state and let the framework handle the rest.
I wrote a ton of backbone code, and was a major fan of jquery before Dom querying was even a feature of browsers. And once I went react I really fell in love with frontend dev. Frontend is so simple now because of it, while it used to be dare I say more complex than backend back in the day.
Same, used jQuery for years and had a love/hate relationship with it.
You're correct that React solved the imperative problems from before, but that's not the only factor to consider. You have to weigh the problems it solves against the problems that it causes.
Eh. For me, it was vastly worth it. Yes there are drawbacks to react. But I feel like the gains far far far outweigh them.
totally fair
Yeah 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".
> 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.
Yeah I like the idea of signals a lot. I haven't played with SolidJS but I'll give it a shot.
That's the worst idea for framework I've seen recently
Seems 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.
Tailwind is gross. It's just that it's the least gross of the available options.
While 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
Yep 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.
JSX without React is also kind of fun: 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.ts...
Agreed! 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
Add me to this club of JSX hackers: 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/
Very cool - reminded me of https://github.com/creationix/dombuilder which I have... memories of. Shout out too to https://microjs.com/ for allowing me to relive some nostalgia.
Everything becomes Lisp in the end.
These are awesome (both parent comments). Love seeing that I'm not alone in my JSX curiosity.
With our powers combined we can reinvent the tiny fun internet.
Yeah 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.
Thanks 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.
I 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.
The 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.
If you want to use JSX without React see this 500-line lib: https://github.com/wisercoder/uibuilder
And here's an app built using this lib: https://github.com/wisercoder/eureka/tree/master/webapp/Clie...
This 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.
Can you talk a little about the pain points you encountered, which this presumably solves?
Sure! 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-r...
In contrast, here's how you would do it in matry:
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.This 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:
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. 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://svelte.dev/blog/runes
> 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.
Yep, 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.
I appreciate the RxJS observables approach with similar built-in cleanup, but also ways to build higher-level tools and compose things together.
Puts the timer up front and center. There's some confusion between "hot" and "cold" to deal and sharing with, but the pipelines eventually clean themselves up and the higher-level operators can be very nice to you if you let them.reactiveX will always have a special place in my heart, but at some point I had to accept that the soup of switchMap, combineLatest, distinctUntilChanged wasn't fun for anyone on the team but me.
If we ever get native stream support in JS, I believe it will be closer to hooks than to rxjs.
Signals could actually be a good middle-ground with how they are pull-based, automatically infer dependencies, don't require complicated Syntax like switchMap and combineLatest, and give more control to the consumer (batching, memoization).
Personally, I have a hard time not seeing Signals as just non-conforming Observables missing a bunch of useful operators. They remind me of how much people loved ko.computed() from Knockout (and I've joked at times that Signals are just a return to ko.computed), but then had an awful time debugging the edge cases or fixing the performance from bad dependencies. Hooks maybe lean too much the other side of the spectrum in trying to force manual dependency tracking but maybe making it harder than necessary. ReactiveX-style Observables hit a sweet spot for me in dependency management that is hard to beat, especially once you get into all of those complicated operators like switchMap/combineLatest/distinctUntilChanged that can be very powerful tools in the right hands, especially for things like dependency management and throttling. (Though yes, getting junior developers and some types of teams all on the same page can be the hard part. In general a lot of functional programming gets tot be that way: once over the learning curve it's all gravy, but the learning curve looks like a terrifying roller coaster to many that haven't yet.)
A proper pipe operator or a C#-like "fluent extension methods" approach might help a lot to clean up some of "scariness" of the first hill of the roller-coaster. If we are wishing for ponies, it might even be really nice in JS to have a proper monadic do-notation, even just a limited form like C#'s LINQ syntax (from … in … let … where … select …). "Monadic do-notation" sounds like its own terrible rollercoaster hill, but is a powerful and succinct way that lets you use a (monad) like an RX Observable in a very imperative ("Signal-like" way that is easy to read, even if the underpinnings feel hard to grasp. (LanguageExt for C# is a fascinating example in how it allows using the from/select syntax to do some interesting monadic binding, that I've seen used successfully by junior developers that don't know what a monad is, but know C# enough and find they can read/write the LINQ well enough.)
I've also had some success eventually training junior developers to think in RxJS or ReactiveX in general. Marble diagrams are particularly great for visual learners to better piece together what happens. (I'm a big fan of reactivex.io's Operators pages for that, and RxJS' marble testing-based unit test harness. I've seen a similar library for C# but it didn't support all the parts of RxJS' syntax which I needed. I'd love to see that need met, too.)
This reminds me of google’s 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.
That's super interesting, hadn't heard about that, thanks.
Hmm, 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?
Using 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:
In this example, the img and aside elements could be anywhere in the tree.Mithril seems like it can do something pretty similar: https://mithril.js.org/jsx.html
(Not trying to discourage you, just linking it)
No 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.
I 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?
It'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.
> 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?
I 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.
This 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!
I wrote jQuery from 2009-2016
And I can tell you liked it! It’s fine, but not all of us did!
Why not use template literals and a css like syntax for selecting and update?
setContent(css`p[key=counter]{ content: 'The count is ${count}'}; }`);
Great 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.
React, the good parts
Yep, 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.
[dead]