Hacker News new | ask | show | jobs
by lpghatguy 1566 days ago
New JS frameworks always make for compelling hello world examples.

Can you branch on state or use loops over data in Solid.js? The reason _why_ React has a virtual DOM is to enable more interesting relationships between your data and your presentation. Anyone can make a framework that makes the source code for an incrementing number look pretty!

As an example of this point, check out the "Simple Todos" example for Solid.js[1].

In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

I've been writing React and React-alike code for a long time. I think that fine-grained updates avoiding reconciliation are a good idea, especially for performance. At one point, I built a React-like library for Roblox and Lua whose most novel feature ended up being "Bindings"[2], which look sorta like Solid.js state containers. They create little hot-path data dependencies, but the bulk of your components still use normal React-like rendering.

  [1]: https://www.solidjs.com/examples/todos
  [2]: https://roblox.github.io/roact/advanced/bindings-and-refs/
8 comments

>In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

I find this a totally bizarre complaint. I've spent the past few months working on Svelte stuff and I've seen people on HN make this same complaint about Svelte's templating language with {#if} and {#each}. Who cares? What is so wrong, exactly, with "reinventing a concept that's already in the language"? It does not make code any harder to understand or to write, and it does not harm performance (in this case, quite the opposite).

I would much rather have a reactivity model where I plug in completely standard concepts and patterns (a for loop) than one where I have to deal with a bunch of framework-specific, complicated ones (hooks). That Solid's reactivity primitives are familiar is an advantage, not a disadvantage.

Because you sometimes want to filter, sort or project your data. Then you have to handle this in viewmodels or invent more and more features for the templating language. Then you want to refactor into components. So you need facilities for invoking subcomponents. Maybe you want something recursive to display tree-like data.

So you end up with a secondary full featured language usually with worse IDE support, worse error messages, more surprising issues, etc. You need to understand the scoping mecanisms and if things go wrong hope there is a debug tool available.

And in the end those templating languages do not prevent you from mixing UI responsibilities from the rest of your code.

If you want a reactive model you can have one. I personally prefer explicit messages like calling setState.

> Because you sometimes want to filter, sort or project your data.

The idea that this type of thing should be happening anywhere near the view rendering loop is the exact reason I've not had a great time picking up React codebases.

By the time you're rendering data into markup, the data should be in the exact state you need it. No further filtering or data mangling or sorting. That type of data manipulation should happen at the point of data change and then it shouldn't happen again until the data changes again.

The simplistic templating languages in Vue/Svelte/Alpine/whatever-comes-next force you to pull your data manipulation back to somewhere more appropriate, with Vue even throwing a warning if you try to filter within v-for construct.

Because React is JS, people are let loose to do wildly inefficient operations and do them over and over and over whenever _anything_ in that component changes.

I love vue’s concept of computeds. It makes me think back to knockoutjs when things felt like they “just worked” as long as you knew where the ES5 footguns were.

It’s nice to have a concept “ground truth” in data and props and then computeds that sort of tie it all together.

My feelings exactly, it makes for a satisfying separation of concerns, and if you understand what's going on under the hood it makes for cleaner templates and more obvious component code.
Yes, and those are available as well with MobX (in React), Svelte, etc.

You can do same/similar with React hooks, just not as clean or obvious.

You can 100% do these things in React, I don't believe React is a less able framework by any means. If anything it gives you a powerful toolbox and pulls down the guard rails.

I do, however, think that working with React changes your mental model somewhat, and when I'm working with React I catch myself doing a lot more data wrangling close or in the rendering loop than I would in any other modern framework. Certainly since class components have fallen out of favour, you're working with a function designed to be run hundreds of times, while Vue and Svelte both provide clear patterns to deal with data at the point of change, then separately deal with updating the display of that data as required.

It takes using something like MobX to really push a React codebase to a data-driven model and that means many inexperienced developers fall into the common pitfalls far more easily than if they're using an alternative framework imo.

Exactly. The whole paradigm of reactive data binding is that data dictates what the UI should render. Don't solve your data needs when rendering ffs.
> So you end up with a secondary full featured language usually with worse IDE support, worse error messages, more surprising issues, etc.

Gilad Bracha calls these "shadow worlds" https://gbracha.blogspot.com/2014/09/a-domain-of-shadows.htm...

Haven't used solidjs before, but I paid the doc section a quick visit and saw that it's basically <For each={foobar}> where foobar can be whatever javascript code you want. So you can certainly do filter/sort/project on your data before rendering.
You’d probably not want to project inside that loop though, but before it, in a reactive effect.
A react feature that I appreciate is that it is "just javascript". It's easier to learn how to loop or have conditionals in React because it uses native JS features. It makes it easier to understand, for me.

Having templating DSLs in other frameworks isn't a deal breaker, but it's a pro of React that I appreciate.

But I mean, it's not really. A hook invocation looks exactly like a javascript function invocation. Except it's subject to hook rules. Those don't come from javascript. Those are language rules that come from react. And even worse, the syntax for invoking a hook is exactly the same as the syntax for calling a function.
I've not used Svelte, but when I've used such DSLs the problem tends to be that they're not very flexible, and as soon as you step outside of the provided helpers you're stuck and you just can't do the thing.
Do you have an example you've run into where a DSL such as Svelte's or Vue's has actually stopped you from doing something? Would be genuinely interested to see it as I've never run into such a situation myself.
It's true, but the counterpoint is that DSLs make your life much easier in 90% of the cases (if not more).

Personally I've never found a problem I wasn't able to solve in Svelte.

> It does not make code any harder to understand or to write,

Well, what can you tell me about TypeScript type inference for this custom DSL?

I wouldn't call <For> in Solid a custom DSL anymore than any given React component. And Solid looks to have good TS support.

https://www.solidjs.com/docs/latest/api#%3Cfor%3E

Have you used Vue 3? There is fantastic TypeScript support in templates, including comprehensive intellisense for the templates in VS Code with the Volar extension. Surprisingly, _refreshingly,_ good.
> What is so wrong, exactly, with "reinventing a concept that's already in the language"?

Nothing, inherently. Just like there's nothing inherently wrong with having extremely clear and simple rules for how to use hooks, and lint rules to identify when you're not following those rules. Nothing inherently wrong with either, some people just have strong distaste for one or the other.

> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

Once you deal with larger amounts of data and need virtualised rather than fully-materialised lists, you start using different things in React as well. The fact of the matter is that if you care about performance at all, the simple ways are just insufficient, and the native language constructs were designed for procedural programming, not reactive interface rendering, which requires fundamentally incompatible semantics. It’s not even fair to claim that React uses regular JavaScript idioms—VDOM, hooks, the entire shebang is all about eschewing regular JavaScript idioms because they don’t scale. (OK, so there’s also the matter of transient state like scroll positions, element focus, and form field values; it’s not fair to say that React does all these things purely for performance’s sake, as the naive immediate mode approach would also break such functionality.)

> virtualised rather than fully-materialised lists

I want to push back somewhat on this practice. Our computers are fast enough now, and the browser implementations optimized enough, that they should be able to handle thousands of materialized list items without breaking a sweat. Sometimes you really need virtualization, e.g. if the underlying data source has millions of records. But if the data can be fully materialized, then the implementation is simpler, and the user can take advantage of things like find in page. Virtualization is a convenient way to avoid the inefficiency of unoptimized VDOM-based rendering (e.g. with React, and yes, I know there are other optimizations available in React), but fine-grained updating (as in Solid) is even better.

A few hundred, sure; a few thousand, it’s starting to get a bit iffy.

It depends a little on the complexity of the rendering for each item, and where you are fetching the data from, but when you’re into thousands of records you’re very likely to need at least some partial rendering. Suppose each record’s data is one kilobyte (I’ve seen far lower and far higher), then one thousand records is already one megabyte, which for many people will take multiple seconds to transfer, so you’ll still want to load the visible records before fetching more, or do streaming parsing of the records as they come in. And that’s ignoring the backend’s performance on fetching records, which must be taken into account too.

People are certainly often too eager to reach for virtualised lists, or worse still lazy loading without reserved scroll height, but even at a thousand records with simple rendering they’re still probably generally warranted—fast computers can cope with comparatively little visible difference, but on slower ones (especially older and cheaper phones) you’ll easily feel the difference. Memory usage can also be a concern for larger quantities of data and DOM (1000 × 100KB = 100MB).

I’m saying all this as one that scorns React and VDOM stuff in general as unnecessary performance overhead, and likes to use Svelte and plain JavaScript and things like that (or better still, to eschew JavaScript); and who worked on Fastmail’s webmail, which certainly uses such progressive-loading lists, on a precise-DOM-updates framework that cares significantly about runtime performance. React and its ilk are certainly particularly prone to using virtualised lists as a crutch to work around their shortcomings.

All that being said: yeah, I wish things like Discourse would stop doing aggressively lazy loading when there aren’t even several hundred comments in a thread. Render just the things on screen to begin with, if you must (though it’d be better to just send real HTML from the server and let the browser take care of all this, even if it complicates your JavaScript loading), but then load all the rest straight away so that I’m not penalised just because I’m on the other side of the world from the server, and let the browser handle in-page search.

Aggressive virtualization, especially if it also involves removing stuff that scrolled out of view, also bogs down some screen readers (particularly Windows ones) that have their own representation of the web page.

Speaking of both Discourse and screen readers, before my stint at Microsoft, I wrote a Windows screen reader, which tried to detect client-side page navigation by watching for the URL (minus the fragment) to change. Discourse's infinite scrolling implementation broke this heuristic, because Discourse would use the history API to update the URL as the user scrolled. Not sure if I or they were in the wrong there.

Interesting that you feel that Discourse penalizes you for being far away from the origin server. When Discourse was new, one of the founders blogged about how their heavy use of client-side JavaScript made the application better for users far away from the origin server:

https://eviltrout.com/2013/01/06/turbolinks-and-the-prague-e...

Maybe the author had a point, but it sounds like Discourse still relies too much on frequent round trips.

Edit to add:

> a precise-DOM-updates framework that cares significantly about runtime performance

That's Overture, right? I wonder how it compares to Svelte and Solid.

What JS framework would you choose to work with big datasets like, e.g. a data grid with half a million rows that should have a "filter as you type" functionality?
Most of the work for this is essentially database tech rather than UI tech. From the UI perspective, you just need to be able to say things like “current query is ‘foo’, and based on my current scroll position I want to render records 32–86” (since 42–76 will be visible on screen, and then we add a few more for good measure to give a small time buffer for retrieving more when you start scrolling), ask the database layer for the required records, and render them as a perfectly normal virtualised/only-partially-materialised list. It’s then up to your database layer to perform the filtering; whether that database runs on the frontend or backend makes no difference, and whether it’s sqlite.js or records.filter(…).slice(…) makes no difference (though their performance characteristics will certainly vary). This can be integrated with the UI framework fairly tightly, but there’s no need for it to be.

For the UI part of it: what I would use would depend on my requirements (is it a list, is it a grid, how is it to be interacted with, &c.) and what was already in use (React, Svelte, plain JavaScript, other). I personally would often be inclined to implement it from scratch, because I’m typically not impressed with most library options (they have a tendency to be heavy, limited, and slower than they need to be) and am familiar with exactly what needs to go into it to make it as perfect as is possible (it’s not a particularly large amount of work, but it is fiddly in places and must be done correctly or it’ll be awful), but that’s not a course of action I would recommend for most developers.

Doesn't matter which framework if you implement it right. Filtering data from the dataset is plain js, and it will be slow. Rendering the data to screen requires a lot of work, such as using offscreen-buffers for smooth scrolling etc. Actually ag-grid does this pretty well, and I've used it for similar in a React app. Now hold your horses, ag-grid is angular 1.x, encapsulated as a component. Another proof that the framework is not important, you need to optimize hell out of it.
Having recently shopped around, and implemented, this kind of data grid: no JS framework actually handles the hard parts, but (more-or-less complete) libraries exist for nearly all of them.

My specific use case is a React application, and I have found it easier to implement a dedicated listener system, than try to fit things into component states.

Isn't that what a backend is for?
You shouldn’t do that kind of work from the rendering thread, and you should use occlusion culling to only render the needed DOM
Hence my question, is there a datagrid for any JS framework that does all the hard stuff for me?
>…much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

I'm right there with you, but when React invents a whole markup language inside of JavaScript, it's not in much of a standing to make purity criticisms.

Well you've obviously completely missed the point of JSX then. The "whole markup language" that they invented is literally a line for line transform. Optimisation aside, there's no reason why line 58 of a file with JSX in it won't be line 58 of the transpiled JS file, and read exactly the same. All JSX is is a custom function call syntax.

It's about as pure as you can get while having any sort of html-ish 'templating' whatsoever.

So, I'd say it's exactly in the right place to be making purity criticisms. They've taken the only approach that preserves the integrity of the code and doesn't involve build time magic.

Solid's <For> doesn't rely on any build-time magic, other than the JSX custom function call syntax that is also present in React. If you want, you can even call it like an ordinary function:

    function MyComponent() {
        return For({
            each: [1,2,3,4],
            children: x => <div>{x}</div>,
        })
    )
It's just a function call, and it doesn't even need `React.createElement`. What's more pure than that?
Normal for statement is more pure than that.
Functional purity? `for` can only have side effects. It has no functional purity. It doesn't even have a value.
Imperative purity.
Oh I missed the point on React altogether. After 10 years, I still fail to see why anyone would use it voluntarily.

But coming back to JSX, a custom function call syntax is OK and pure because it has a one to one mapping on line number?

I don't know if there's much point in discussing purity since it's badly defined and mostly in the eyes of the beholder, but it always smelled like one hacky syntactic sugar to me.

That is an utterly bizarre rejoinder. JSX has always been (and afaik still is) optional and extremely thin syntactic sugar. It’s little more than a convenience macro (so that views can look a little more like the markup equivalent).

It has no intrinsic semantics, and maps pretty much directly to actual javascript (which you can write directly or use an alternative helper for — hyperscript being a common one).

> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

This argument is silly because it inevitably becomes a pissing contest of who can be most like vanilla JavaScript. In that case, why use JSX? Just write hyperscript calls instead. Why use React Router/React Context helpers? Just wrap your components using vanilla function providers instead. Why use React Hooks, which inevitably look like magic to a JavaScript veteran because the library inherently hides away some global state? I hope you can see what I'm getting at here.

> In React, we render lists by using regular JavaScript idioms like loops, arrays, and array methods like map. However in Solid.js, much like traditional templating languages, we get a construct like <For> that reinvents a concept that's already in the language.

I had/have your bias, but from playing with it I found a couple things:

1) Like React, you can swap out the template feature for a function call (or subcomponent). e.g. instead of

  return (
    <button...>
    ... 
    </button>
    <For each={state.todos}>
    ... 
  );
you can use functions and loops:

  function displayTODOs<T>(todos: T[]): any {
    let arr: any[] = [];
    for(let [i, todo] of todos.entries()) {
      const { done, title } = todo;
      let elem = (/\* JSX \*/);
      arr.push(elem);

    }
    return arr;
  }
  ... 
  return (
    <button ...>
    </button>
    {displayTODOs(state.todos)}   
  );
2) Even with my bias, I must admit I found the `<For...` syntax to be surprisingly easy to read and fast to eye-parse; much more so than other 'templating' (using your term) languages/macros/syntax I've used over the years.
Likewise I simply don't empathise with the author's complaints. Hooks make sense if you think in closures. Hooks are isolated so you can think about them in isolation.

What I like about the React monoculture is that it's one less thing I have to care about. I can focus on the other aspects of my programs, beyond turning JSON into HTML.

I haven't used SolidJS so I'm not going to put it on blast. However, hearing people compare its reactivity model to Knockout JS gives me the heebie-jeebies, because Knockout projects were horrific to reason about (and test) beyond a certain scale.

> we get a construct like <For> that reinvents a concept that's already in the language

Isn't this optional? Can't Solid use regular JSX loops?

https://www.solidjs.com/docs/latest/api#control-flow

For reactive control flow to be performant, we have to control how elements are created. For example, with lists, a simple map is inefficient as it always maps the entire array.

This means helper functions.

This feels like we're trading complexity here for complexity there, and it seems impossible to judge which way is actually "better". I use loops in React all the time but only have used `setInterval` in a component a handful of times..
Most of our jobs is determining the right trade offs.

I don’t see the big deal here. Error boundaries, suspense, context, very popular routing libraries all have used components to encapsulate functionality. That’s to say first party and third party functionality in the React ecosystem have gone down this path.

We are not trading some complexity here for some complexity there. We are trading a huge amount of complexity for a framework that is simpler by an order of magnitude. Simplicity is one of the really undeniable benefits of Solid once you gain a decent understanding of the framework. React at times might appear simple on the surface but the overall complexity is pretty huge compared to Solid.
I believe there’s a finite, fixed amount of complexity in problems; that there is, categorically, no way to solve problems without that amount of complexity that is inherently part of the problem domain.

So, what you have here does not remove complexity from problems, it moves the complexity from one place to another.

So, when you have a simple task, and a straightforward framework, what you see is “it’s easy!”. …because when you use the complex framework you get a bunch of “solutions” to problems that don’t exist on your problem.

That’s why it appears overly complex.

…but for a complex problem, when all you have is a simple framework (like <For…>) you have to implement the complexity yourself, which makes you view the framework as feeble and under whelming.

So, you are just moving the complexity from one place to another; the question is, is the complexity of react really something most people need, or can a framework like solid solve the 90% of simple problems most developers have?

It’s hard to tell.

Most new frameworks excel at solving simple problems because it makes for cute demos.

Is solid any different?

That’s my question. How does it work at scale, for large complex projects? Is there a whole design system implemented in it? Who’s using it and for what?

The claim that it’s “not complex” doesn’t help.

All that means is there are probably a crap load of things it doesn’t include I’ll have to do myself.

There is no magic bullet that removes complexity from tasks.

React is a complex beast, and a nice clean framework to replace it would be welcome.

…but you have to approach this kind of discussion honestly.

Hello world examples are a dime a dozen.

I feel like if it were actually an order of magnitude less complex, it would be eminently obvious from a blog post about it? Maybe I'm just tired and don't "see it" for whatever reason, or maybe I need to find some better examples.

From the moment I saw a post about immer.js, I was sold because it seemed like an obviously better solution for the vast majority of cases where I would otherwise grab Immutable.js, a library that I wanted to like but inevitably struggled against.

This.. isn't quite as revelatory. I'm not saying it's not all that you claim it is, it's just that from a glance, I don't see how this somewhat different approach addresses the problems I run into often with React in a major way (beyond the claimed performance boost).

immer.js can have terrible performance for data structures starting already with 100 of elements. Using JS proxies is not cheap.

We have found that continuing to use immutable.js Map and List but using plain JS objects, not Records is sort-of a sweat spot. But one needs to enforce immutability with Flow/TypeScript read-only types and use the latest immutable.JS to make it work.

My library (see other comments) has components that are about half the amount of code as react or solid and in pure javascript. The javascript shows exactly was is happening, as opposed to the "simplicity" of react that shows a pretend version of what is going on, then 5 years later people discover that is a bit of a problem and now we are onto the next library (solid?).

I wonder if I could have your opinion on my library, and why half the code, native performance is actually more complicated in the long-run. I don't know the answer at the moment. Documentation isn't complete... it is just web components. eg hello world just becomes a function like const component = hello('Andrew'); document.body.append(component); in the example on github. Anyway, the todo shows a more typical real world example I guess.

JSX doesn't have loops. When using React, you use regular not-React-specific Javascript tools to do loops and create lists of React elements.
If this is true, why do you need unique item keys? React cares about loops a lot more than it may seem at a first look.
keys are not specific to loops. They are needed whenever you are returning a react document with a dynamic structure. They allow for more fine grain dom updates.
>The reason _why_ React has a virtual DOM is to enable more interesting relationships between your data and your presentation. Anyone can make a framework that makes the source code for an incrementing number look pretty!

Actually, that is only half the reason. You can do whatever you want in my library (github.com/thebinarysearchtree/artwork) and it doesn't have any kind of virtual DOM or whatever Lit does, because you just create elements with JavaScript. The second reason, that everyone just assumes is the default, is that React has to use HTML-like templates and not just JavaScript.

JSX is a purely optional part of React. You can use it with just Javascript and no bundlers just fine.