Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Like others have said - I think the documentation glosses over how the "rerender" function totally and drastically changes this from a "React alternative" to "backbone with JSX".

The point of React is not the JSX, but rather the unidirectional data flow and automatic re-renders due to prop changes -- essentially modelling your application as a "state machine" and eschewing the need for a "view model" or manual synchronization of DOM state to application state.

With the "rerender" function I fear any nontrivial application will devolve back into the tangled web of event listeners we left behind when moving to React.

Personally - I think a library like this is only useful for a very narrow audience: people with an affinity for JSX that are only building webpages (not web "apps") with some basic progressive enhancement features that can be kept track of pretty easily.

I don't mean to be overly negative here - I just suggest that you alter the documentation to be more clear about the differences/limitations when compared to react. The existing docs/example is almost (unintentionally, I'm sure) misleading in my opinion.



> With the "rerender" function I fear any nontrivial application will devolve back into the tangled web of event listeners we left behind when moving to React.

Disclaimer: I'm the author of Mithril.js (A React-like framework that got mentioned in some other comments). I do think that the naive approach of eager render (a la backbone) can a bit limiting in terms of large scale applications, but I think the question of scalability is more nuanced than whether a rerender function exists.

I don't think calling a function vs not calling a function is necessarily a meaningful distinction: React's own `setState` is a form of "rerender" function.

The "devil in the details" comes from what the semantics of re-rendering are, and how that ties to the overall philosophy of the library. In React, `setState` abstracts over batching, meaning that one can, for example, call dozens of setState hooks from an event handler and not have to worry about multiple repaints. But on the other hand, if you're sharing state between, say a list view and a counter pill in different areas of an app, that involves a bunch of extra code (be it data-down, events-up, or using Context or redux or whatever)

On the other side of the spectrum, too much implicitness isn't necessarily good either. Angular.js had some notoriously difficult to debug rendering loop issues when you used watching in certain ways alongside Angular.js' auto-render system.

In Mithril.js, the default is that event handlers do not require explicitly calling a rerender function at all in the first place, because it starts from the assumption that 99% of the time you do want to rerender when an event occurs. But Mithril.js does also offer explicit bail-out-of-auto-render, as well as immediate rerender and batch rerender functions because in some rare cases you do want to control exactly when to render (e.g. you might want to actually double paint if part of your state is computed from bounding box measurements, or you might have multiple third party integrations in different areas of an app that render after another third party async computation completes, and ALL of that needs to be batch-rendered if they fall in the same event loop cycle).

React's philosophy is very much to always think in terms of state snapshots, and the fact that its APIs are largely declarative reflect that. Mithril.js can also be used in a declarative fashion, but it's not as prescriptive. It also allows the developer to think in terms of timelines, and procedural APIs excel for that use case. Ultimately, though, good design comes down to being deliberate about what kind of semantics works well for what kind of mental model.


As others have already pointed out, it's a bit of a misconception about React, certainly not helped by it's name - it is not truly reactive.

Think about all the ways you can trigger a re-render: either `setState`, `forceUpdate`, dispatching an event, or some other kind of update function from a state management library. It doesn't react to any kind of assignment like would happen in Vue or Svelte. They do exactly the same as your `rerender` function here, it's just nicely hidden. There will be no more of a mess of event listeners than you'd get using React.


Sure, it doesn't react to assignment, but it still encourages the immutable data patterns of a more functional/reactive programming style.

The docs for Forgo here show a lot of direct DOM manipulation and/or direct assignment to mutable data structures, which you then have to synchronize.

I personally almost never have to think about these things when I build with React and treat everything as immutable / pure. Then things update according to reference inequality / shallow diffing of props. You can even update a prop at the top of the component tree and it won't re-render children that didn't actually change (and without you having to orchestrate that yourself).


> you can even update a prop at the top of the component tree and it won't re-render children that didn't actually change

That's true; the upside of immutability was supposed to be more efficiency when doing reconciliation. But in reality every large React app I've ever seen re-renders entire swaths of the component tree unnecessarily due to complexity - especially now with Context, nullifying that.

It seems that a whole lot of people actually prefer mutability. See the popularity of MobX, and even libraries like seamless-immutable actually have the goal of offering a mutation interface that generates immutable objects underneath. If you can have mutations and correctness, sounds like a good deal. I've been using Svelte a lot recently, and it's a breathe of fresh air to not worry about component lifecycle.

On top of that, React doesn't do inequality checks unless you explicitly use PureComponent, or wrap function components in memo(), so you need a lot of manual work to actually get that benefit. It's basically the inverse of having to call `rerender` everywhere.


The fact that mutation is more ergonomic in JS and that React isn't doing that much by default to take advantage of immutability is a fair point. But an important distinction to make is that React is actually not _able_ to take full advantage of immutability by default today in the JS ecosystem because it depends on passing around mutable JS data structures, that don't have value equality semantics outside of primitive types like strings and numbers.

In the case of React wrappers in functional languages, such as ClojureScript's Om and Reagent (and probably many others, but those are the ones I'm personally familiar with), components actually have the PureComponent optimization enabled by default. This is because deep equality checking comes for free due to value equality semantics of the underlying persistent data structures offered by the language.

The PureComponent optimization itself ends up becoming a much more effective global optimization in these languages, as it ends up benefiting the vast majority of use cases, whereby rerenders get optimized away as long as the _values_ getting passed around aren't changing, regardless of how or when those values are produced.

Contrast that to the case with JS's mutable data structures, where the only time when PureComponent can save you from rerendering is if no references (as opposed to values) change, which is much rarer to come across in practice (in fact they change by default for every computation you make on every render), so it's something you end up having to deliberately optimize for (although the ergonomics of the tools for actually making these optimizations has come a long way since the class component days with hooks like useMemo and useCallback).

This is why PureComponent is not enabled by default as a global optimization for React in JS, because a vast majority of use cases won't actually benefit from it. As such, the cost of actually checking for equality for every component ends up over-weighting the benefits of a tiny handful of potentially skipped renders.


Quietly sitting in my re-frame corner and wondering how JS developers cope with framework-churn.

And, yes, reagent (used by re-frame) nicely combines pure component optimizations with react-style rendering.


I get it's a glib comment, but serious answer because the answer is "same as every other language". JS is easy to write, has a vast ecosystem, a vast number of developers using it. Subsequently there are a lot more people writing their thoughts about it on the internet. There are lots of "new" frameworks, nobody uses them, but because of language popularity the number of people writing about {shiny thing} as if it's used is relatively high compared to other languages (PHP, for example, had the same issue, if it even is an issue).

1. See {thing}, assess whether to ignore it or keep an eye on it.

2. If {thing} is brand new, wait a year or so, then go to 1. If {thing} is a few years old and/or has [possibly suddenly] gained traction and/or provides immediate, obvious and significant benefits (assessing latter being where lack of experience can cause an issue), go to 3.

3. If {thing} is only starting to gain traction, wait a year or so, then go to 2. If {thing} has gained traction and is battle tested go to 4

4. If {thing} has traction + community + libraries + docs + tutorials then same as before, but more seriously, judge whether usable.

Early adopters using and writing about it are at stage 1 or 2 which could lead to stage 3 and 4. {Large company throwing resources at thing} may push it up ranking.

React/Vue/Angular and probably Ember are at stage 4. Svelte possibly 3 possibly 4. React/Vue/Svelte/Ember use same underlying paradigm. Would expect a developer familiar with one to easily switch productively to another in a few weeks. Not quite much of a muchness, but close enough. Angular slightly different paradigm. Usage of everything else is so low as to be irrelevant.

At first glance this is at stage 1 and is currently fairly useless. That's not a slight on the developer, just how it is. It doesn't affect any perceived "framework churn" because it will only be tried by very few developers. There may come point several years from now where it becomes useful and widely used (this seems unlikely from skimming the linked site, as it seems to provide nothing interesting, but I may be missing something), but now is not that point.


> You can even update a prop at the top of the component tree and it won't re-render children that didn't actually change (and without you having to orchestrate that yourself).

The number of React apps that render super slowly, with visible delays when typing, is proof that it is easy to make very poorly performing react apps.


> They do exactly the same as your `rerender` function here, it's just nicely hidden

That's the whole point. React does this for you in the background. You don't have to think about it.

> There will be no more of a mess of event listeners than you'd get using React.

There will be more of a mess because you have to programmatically update elements.

With React, I can alter global state and every single element that uses that state gets re-rendered. That isn't possible with this framework.


You are programatically updating elements in React, you just internalized a different set of rules (putting state in the designated buckets vs calling an update function). It is the equivalent of this:

    const setState = (s) => { state = s; rerender() };
Real reactivity is what you get with proxies in Vue, or a smart compiler like Svelte.

> I can alter global state ... that isn't possible with this framework

You'd just need to export a state object from a module, import into the components, and call rerender after any mutation. I've actually written quite a few vanilla-js apps using this structure when I don't want to bother with tooling, small pages don't need a complex reconciler.

I'm not saying this is better btw - there are ergonomic concerns and different trade-offs, but the distance from this to what React does is much, much shorter than most people think. We've just been conditioned to accept its rules as 'normal': don't declare plain variables in your functions, don't mutate properties, don't use inline functions as props, don't rely on previous state object for updates, and so on... step outside the box and you'll get a mess too. This library is just a smaller, different shape box.


I believe I have misunderstood how this library works, and I agree with what you're saying.

At first glance I thought that rerender(args.element) referred to the element that the method containing rerender(args.element).

Hence, I thought that if you altered global state in a component and called rerender(args.element) it would only rerender a single element and it's children rather than the entire component.

Looking at the code more closely I see that args.element actually refers to the component, so the rerender function does do a full rerender.


It’s not completely equivalent, since React avoids rerendering parts of the tree when the state has not changed (=== comparison). In some ways it separates the concern of rendering from state changes.


The phrasing on that statement sounds a bit off, and I'm not sure whether it's a real misunderstanding of how React works or just the way the sentence is written.

React re-renders components recursively by default. If I call `setState()` in a component halfway down the tree, React starts its rendering pass at the root component, then skips quickly down the tree until it runs a cross a component that's been flagged as dirty and needing an update. From there, it recurses down through all descendants of that component and re-renders them as well, regardless of whether those components have any queued state updates or whether there are any changes to their props.

For more details, see my extensive post "A (Mostly) Complete Guide to React Rendering Behavior":

https://blog.isquaredsoftware.com/2020/05/blogged-answers-a-...


Re-rendering for state changes is a fairly easy thing to do, if you're willing to accept something like useState/setState. It can even be done application-side (without help from the lib). Basically, just wrap all state changes in setState and re-render everytime.

Now the optimization I haven't done is deciding whether a child needs to re-render when a parent re-renders. Comparing the props seems like a straightforward way to do this, so I'll probably spend some time over it today and implement it soon.


Yep sorry, I misunderstood how your library works.

I left another comment explaining what I misunderstood: https://news.ycombinator.com/item?id=25800961


I've been building some projects with Crank.js and Mithril, both of which require an explicit function call to rerender the application after changing state.

In theory, that sounds terrible; I don't want to keep track of when my app needs to rerender, or worry about a stale DOM because I forgot to rerender after some event.

In practice, it's a breath of fresh air compared to my work with React/Vue/Svelte. Everything is just so simple; I can use vanilla JS data structures and code organization patterns. I can architect my frontend state and logic in the way that makes the most sense for my domain, instead of ceding control flow to a heavy, footgun-y, limited-expressivity framework that demands to know everything my app does so it can decide on its own when to rerender.

I'm tired of debugging confusing bugs because React hooks mix asynchronous control flow inside the render loop and my app has to have correct rendering and logic for every intermediate state of a chained sequence of useCallback/setState hooks. Tired of telling my team we have to put off a feature because it'll take a week to do something that feels like a 3-hour task because refactoring around a framework's control structures is harder than refactoring ordinary JS features. Tired of magic reactive compilers that create buggy renders and template languages that are only nice to work with if you use the One True Blessed VS Code Plugin, and tired of frameworks that need CLI scaffolding tools because they're more complicated than anyone wants to initialize with a blank editor.

Crank.js, Mithril, and (at a preliminary glance) Forgo are very compelling to me. They offer the declarative, self-contained nature of components that I love from React/Vue/Svelte and the type safety of TypeScript for view code instead of template strings. My whole project scaffolding is just installing esbuild and generating a tsconfig.json. Maybe toss in browser-sync and watchexec if I'm feeling fancy. I don't need to make major architectural decisions based on the preferred state management tool, because these frameworks couldn't care less about how state is managed. And if I really want automatic rerenders, it's 10 LOC to write a reusable ES6 Proxy that returns an object I can dump state values into that will auto refresh if I reassign a property.

And these frameworks still feel like a state machine in the same way React does - view code is declaratively rendered from application state. There is synchronization of state to DOM, but only in the sense that you have to explicitly invoke the declarative engine, and not in the sense that you have to manually synchronize like you would using jQuery to find and change DOM nodes.

Keeping up with manual refreshes feels so simple and easy compared to all the hoops I've jumped though with the household name frameworks.


Sincere question, how many people work on this application or suite of applications? One, ten, twenty?

It's as the number of developers grow on projects that these things become nightmares. Having worked somewhere that used React and had extremely strict eslint and typescript settings, it was all so uniform (those type checking static linting really keep the guard rails on code quality) because you were guided to use the same effective patterns in solving those problems. In fact, all the interesting work started happening outside interactions with the framework (and more around redux, in this case) so we were more focused on business logic and other concerns, react fell straight into the background. There were only ~25 developers or so. The ecosystem and the framework really lent itself hand in hand. That shouldn't be left out of discussions like this.

I can see this model falling flat on its face after about 6 developers working in this system, especially since there is no adjacent tooling that specifically made to keep the guard rails on code quality.

I'd love to hear a counter example to that.

Edit: my point is, in order for something to be an acceptable alternative to React, it needs to have the ability to thrive in an example like I gave. React isn't for the 1 man shop, per se, or even perhaps a team of 5-6 developers that can coordinate well. Its real strength is in large application development, with `n` number of developers, where n is greater than 6. Thats just been my experience.


I appreciate your response because it brings nuance that is (in hindsight) negligent to leave out of this discussion.

My projects can usually count all of the developers on one hand, often with fingers to spare. I get put on a rapid-file bevy of small projects rather than a single long-term project that grows into a large team. Time-to-MVP and rapid iterative redesign as the problem domain gets mapped out are the priorities in my projects, and light, non-prescriptive frontend frameworks work well for that in my experience.

I think it makes a lot of sense to define different best practices for one-pizza projects than projects where 25 developers can be described as "only 25". There are a ton of developers at both ends of that spectrum, enough that I wish the broader web developer ecosystem would embrace that distinction.


I'm not familiar with crank.js adoption, but Lichobile (lichess mobile app) and flarum.org are two relatively large open source mithril.js codebases that I'm aware of. They are both several years old and have a decent number of contributors.

IMHO, a lot of Mithril code looks like React code. Things like eslint/tsconfig can be setup equally strictly regardless.

Having seen some truly gargatuan React apps, I'd actually argue that neither React nor Mithril do much to enforce structure aside from the componentization aspect (which, to be fair, is a big part of it).

Ember, for example, is arguably far more prescriptive with regards to code structure consistency.

What I do think is true of React and similar libraries/frameworks (think mithril, but also vue, svelte, etc) is that once you setup a structure of your liking, the path of least resistance tends to also be the happy path, i.e. even "copy-paste" programming generally ends up resulting in always making reasonably sized components, always using the state management mechanism idiomatically, etc.


Thanks for sharing your perspective. I happen to disagree with you on a lot of these things - but I guess I shouldn't be so dismissive about manually rerendering and assuming it won't scale to larger applications.

Speaking to your specific examples: I do agree that some of the new React hooks (especially useEffect) seem to promote an ... idealistic idea of how your component should trigger and react to side effects (especially if you enable the eslint plugins) - namely always due to changes in reference equality. If you need to deal with other things (like an ID changing in a nested object that gets recreated on each render) it can be a big headache and error-prone.

On the other hand - most of these hooks (and libraries and build tools you are alluding to) are easy to modify or use something else if you don't like them. Personally I find that much easier than building web apps like I used to pre-React and dealing with manual state synchronization. But to each their own! I've seen some pretty clean non-React codebases and I've seen some pretty messy React ones, just as I have on the flip side.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: