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.
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.
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":
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.
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.
If preserving the purity of JS is a goal it probably makes sense to not present examples in TypeScript. TS is awesome but if I know TS I know how to add types and if I don't know TS the examples become hard to follow (remembering from when I didn't know TS). I guess it comes down to your target audience but my assumption is Forgo is targeted more towards people who prefer "traditional" for lack of a better word JS concepts.
The biggest red flag I noticed was the use of the "rerender" function. It reminds me a bit of writing code in jQuery and manually having to keep track of when the DOM needs updating. Obviously not a huge burden for something small but would be very interested in how it effected larger apps. Seems like a big tradeoff to have to take on that responsibility again.
Saying that, the general API of the library is easy to understand and I could imagine being productive with it very quickly.
Point taken on TypeScript, I'll clean up that example later today. Perhaps I should move TS to a separate section.
I totally expected rerender to be a bit controversial. But if you look at React, the user does provide (strong) hints in the form of the setState() invocation - which could be done here as well.
But then again, it's a tradeoff. Simplicity, and Python's Explicit is better than implicit.
I disagree. At worst you can let a user switch between a TS / JS code block, but really we don’t want to hunt around your .d.ts files to discover what the docs should tell us.
I’d highly recommend keeping TS first class. There are components/tools available to group TS/JS examples in tabs and let the user consume whichever they prefer. (Forgive me for not linking I’m on phone and gotta run.)
Hi HN. I built Forgo because React is no longer "it's just JS" (actually it never was). The patterns, practices and vocabulary used in the React ecosystem present considerable hurdles to a newbie - hooks, reducers, actions, flux etc. All of them are React specific, and locks the developer into framework related knowledge.
But there is one idea from React which made a ton of sense; from the time I started using it nearly seven years back. That's JSX - instead of shoehorning expressions into markup, move markup into the programming language. Forgo embraces JSX and skips most of the other ideas in React in favor of a more plain vanilla approach.
I don't consider reducers, actions, redux/flux to be React specific, even if they are popular in the React world. I know just as many people who don't use those in the React world. IMO what React did was push forward the single data flow paradigm.
What should a developer do that needs to share state across a Forgo app?
It would be awesome if browsers just had a real template language like JSX, we'd see more "web components" which are just a little bit of JSX/or whatever templates and some DOM APIs. Developers are tired of managing binding and unbinding events. React does that well. I assume Fargo does too.
So putting aside the redux/flux paradigm, what else is the real hurdle being solved?
> What should a developer do that needs to share state across a Forgo app?
We already have many facilities in JS to handle state. Singleton patterns, closures etc. The example on the website uses closures to handle component state - which is simpler than React's hooks based approach.
function SimpleTimer() {
let seconds = 0; // Just a plain variable
return {
render(props, args) {
setTimeout(() => {
seconds++;
rerender(args.element); // rerender!
}, 1000);
return <div>{seconds} secs have elapsed...</div>;
},
};
}
> I don't consider reducers, actions, redux/flux to be React specific
They didn't emerge from React - but it isn't possible to do React without them. When I conduct trainings, I often see some of my students go - wow, why would you do that? So Forgo basically is an exploration of how to simplify things - and to let users pick their desired level of complexity.
I understand where the Redux terminology gets mixed up with React and people believe they're one and the same, but it's totally possible to do React without touching reducers, actions, or Redux.
Maybe you mean it's not realistic to write React "beyond toy examples" or "in the real world" without Redux, but still, I'd beg to differ there. You can get very far with plain React and the context API and other libraries. Redux definitely isn't required.
Forgo does look cool though! I like the idea of using plain variables for state, and returning an object allows you to add other things to that object (didMount, didUpdate, unmount etc).
Yeah, even with large-ish projects, just by refactoring some of your state manipulation into separate libraries that you manually hook into your top-level components, you can get by quite fine without a whiff of Redux.
That said, this is a cool framework, congrats on completing it! I'm also working on a minimalist React-ish framework though in a very different approach.
> They didn't emerge from React - but it isn't possible to do React without them.
It is 100% possible to build large React apps without redux/flux. There are entire ecosystems built around, say Mobx, among many other libraries and tools which don't even follow the flux style patterns.
> The example on the website uses closures to handle component state
I'm not asking about component state; I'm asking how do multiple components access app-wide state. I can't use a singleton for that; how do I know when that singleton is updated? I guess I could use a proxy but I don't want to do that.
> and to let users pick their desired level of complexity.
React is the view layer. It doesn't enforce any type of data layer or state management layer.
> I can't use a singleton for that; how do I know when that singleton is updated?
It's up to the developer to make that decision. An AccountInfo control could be listening to "ACCOUNT_INFO_CHANGE" events (emitted when account info changes), and can subsequently rerender and read off some singleton state. Rerenders are explicit.
And like you said, proxies could be another option.
> They didn't emerge from React - but it isn't possible to do React without them.
Here is a React/WebGL game I made which tracks a lot of variables and state, but does not use redux/flux...because it's 100% possible to make even make a 3D game with React without them!!!
> The example on the website uses closures to handle component state
This looks a lot like closure components from Mithril.js[0], which have been around for several years. It's nice to see people independently come to the conclusion that this approach is simpler :)
I work on a templating library called lit-html that aims to just solve the declarative DOM w/ property & events bindings problem: https://lit-html.polymer-project.org/
It's plain JS with a very similar DX to JSX, and I think addresses that really well.
My team also works closely with Chrome, Safari, and Firefox on proposals like Template Instantiation that aim to build up more APIs that template libraries can use to be smaller, faster, and more interoperable.
I really think we will eventually get good templating in the DOM so at least the most common use-cases are taken care of.
Where is the working group at with that proposal? I've been eyeing it closely, but it hasn't seen any significant movement in over two years, last I checked. I'm excited about template instantiate, though I worry the API is going to be tied to having template element instances, instead of being part of the HTMLTemplateElement and its own thing I could take advantage of with string templates. I also haven't seen any place where I can give feedback formally on the proposal to the working group :(
HTML imports were supposed to be the holy grail to solve this, but nobody but Chrome supported it :(
Every time I look at lit I get this unsettling feeling that it’s almost what I want but can’t be, because it loses the only thing I want in JSX. Lit fundamentally targets templates with HTML output. Which is great for what it is. But the thing JSX offers that nothing else does is:
- return values are a data structure, so they’re render-target/render-implementation agnostic
- it’s compile time only/effectively a macro, so the compiler can choose how to render subtrees
I love the “it’s actually part of the language not a build tool” part of lit, but it can’t satisfy those other goals without giving up its core selling points.
lit-html also returns a data structure - strings and values. You can interpret the strings however you want.
JSX actually doesn't return a data structure. It's syntax only and some transforms do things quite different from what React.createElement() does. And this is part of the problem with it and why it can't really be standardized - there's no semantics to standardize.
lit-html as an render implementation does indeed target only the DOM, it's not trying to target non-web platforms.
Sorry, I misstated my point. JSX is itself a data structure, not interpolated strings and values. That makes it closer to hyperscript. But it doesn’t necessarily return anything or have a runtime value at all.
The problems you cite are exactly what I meant to say are its strengths. Because it’s entirely undefined syntax that only corresponds to data, it can be used as a general purpose macro for anything from React.createElement to defining a web component to defining an output for a CLI to defining a test or an API endpoint or anything at all. It’s a general purpose DSL, and the fact that it looks like HTML/XML is just convenient for its most common usage.
JSX is an expression with structural semantics. It doesn’t have to be compiled to a data structure but at the AST level it definitely is, which is the whole point.
React is nothing new - these patterns that are being used have been with us for decades. Sure you can go about programming without knowing these patterns, but looking for ways around in order to know and understand less is a dead end. If you can't understand what hooks, reducers or actions do, why not spending more time on it? Sooner or later a newbie will encounter more complex project and having themselves shielded from knowledge will likely cause them to give up.
That's a very general statement. I wouldn't accuse you of implying that it would also apply to e.g. Date.now(), but I firmly disagree that it applies to hooks.
I get that hooks are declared in the form of functions, and that pure functions are the desired pattern in many cases. But I like to think of hooks as hooks, not functions (emphasized by the naming convention of prefixing with "use"). We are in the context of React, after all, and I think it is comparable to critique of JSX because of "separation of concerns". Yes, valid critique in many cases, but not necessarily applicable to React.
Having been a React developer for over 5 years now, I think hooks solve a lot of problems that before would typically have been solved with Redux, Redux Saga and other libraries. Or with home made solutions, leading to various degrees of mess. From what I've seen, hooks have enabled React devs to write code that is even easier to understand, while also keeping things simpler and external libraries fewer.
> That's a very general statement. I wouldn't accuse you of implying that it would also apply to e.g. Date.now()
You don’t need to, I’ll gladly go on record saying Date.now() is not a function. It’s a subroutine. The clearest signal isn’t even its return value, it’s the fact that it takes no input.
> [... everything else you said ...]
I don’t have a problem with the expressiveness of hooks, nor the general way they solve a problem by defining clear APIs for interacting with reactivity and state. I have a problem with the context in which they’re called, and the way they infect their context by changing the semantics to be sometimes else.
Hooks are something you instantiate and use in your component’s lifecycle. If it were that simple, you could call them in an outer function and return a component. But because you call them while defining the component’s behavior, your component is no longer semantically a function of props, it’s a constructor.
A better API would be hooks called in a component factory, returning components which use those hooks.
Hooks are not something you instantiate and then just call later, the whole point is that they need to be able to chain/compose with each other, and that has to happen during every render. I'd be really curious to see what your proposal is in concrete code, as on the surface it sounds like it either misses the point of hooks entirely, else it's just a syntactical change that is more a surface-level critique than an actual criticism of the concept itself.
Also your subroutine comment makes little sense. A function that returns different values over time is just that, it need not output anything. In UI programming I guess you'd call everything a subroutine, as state is constantly changing and you're always in need of outputting changing dates, inputs, etc.
The whole point of frontend is you have to accept that fact and find the best paradigm to deal with that constant change of state, and I'll claim hooks do that better than anything before it. If you did have a better solution to hooks, that'd be a big deal, and many people would love to see a good example.
Thank you for elaborating! I remember I was really curious when they introduced them, since there had been a lot of focus in the community on stateless functional components before, and the benefits of their purity. Now suddenly they were renamed to function components instead, and as you say, are no longer guaranteed to simply be a function of their props.
I guess what we disagree on is whether that is an issue or not. What problems do you feel the current hooks API is causing, that could be solved with a different API?
Another API improvement case I neglected is just to make hooks HOCs and use existing patterns. They can easily wrap props and return a component with roughly the same API for instantiation.
> Thank you for elaborating! I remember I was really curious when they introduced them, since there had been a lot of focus in the community on stateless functional components before, and the benefits of their purity. Now suddenly they were renamed to function components instead, and as you say, are no longer guaranteed to simply be a function of their props.
Thank you for engaging thoughtfully and with some curiosity! I'm sorry it took me a while to come back to this, but I do want to answer because it's worth exploring.
> What problems do you feel the current hooks API is causing, that could be solved with a different API?
Frankly, once you introduce hooks, you can no longer trust that a function is a function. You can't call it without knowing whether it'll cause side effects. You have to know the hooks internals to know what those side effects actually do, and you have to know the internals of every component before you consider calling a component. It's a breaking change of huge consequence, with no type-level (whether you're using TypeScript or JSDoc or just web docs) indication of what a thing is. So now, allllll functions are not functions.
A way a different API could solve this would be to make the construction and render explicitly separate (just like all of the pre-hooks state APIs for react). Modifying the example from the hooks intro:
This isn't where I'd dust off my hands and call it a day. A more ideal hooks API, with chaining and composition in mind, would not just produce free-floating values and state update functions. You could instead wrap props and essentially have a component-local API for a props/state combination where the component still is just a function of props:
A comment adjacent to yours (which I don't have time or patience to respond to right now) asked how another API would deal with hooks which are instantiated per-render (which... I guess exists but it's mind-boggling because it's not how hooks are explained to the public), take either of these examples and nest them for each state/props case you'd encounter. This is the Python principle of explicit is better than implicit (which means, yeah, writing a bit more code might be a pain in the butt but you'll understand what's happening), and in FP is referred to as referential transparency ("If I call f(x) -> y, y will always be the same for the same x").
It is how hooks are explained to the public, for good reason.
This API, which is one I came up with many years ago before hooks as well, is not hooks, and doesn’t have the same properties you’d want. It doesn’t at all allow composition. Honestly, it feels like you haven’t spent much time with hooks if this is your improvement. It's a great case of "making it look better but removing the valuable properties in the name of purity".
I already explained fairly well why you want hooks to be inline in render, as they need to handle side effects, they need to compose with each other and react to changing values, and therefore they need to constantly run to check for updates. It’s 100% how they are used by everyone.
I hope you find time to actually look at what hooks do more closely and find the power behind them.
Again - frontend always had highly stateful and state-changing components, they were just abstracts in ways that suited your tastes better before. But it is just taste you’re talking about, not some inherent superiority. You have to deal with state on the frontend that’s changing often during renders, and you want to handle it locally, and you need to compose and chain various units of logic. You can try and hide that fact behind more syntax, but it doesn’t change the fact that your inner function you defined is depending on an outer closure that is stateful, breaking your law of no functions that act differently when called with the same values.
Anyway, to each his own, if you don’t care enough to actually understand them and contend with what they solve then I won’t try and force you to!
Of course it does. The most basic programming primitives of composition is functions. Want to compose hooks with these APIs? Call one hook from another. They’re just functions.
Edit: or call one hook-using factory/HOC from another.
> Honestly, it feels like you haven’t spent much time with hooks if this is your improvement
It’s true, I do avoid using them. But I spend a lot of time reading the docs, as well as working with a large variety of different JSX APIs.
> It's a great case of "making it look better but removing the valuable properties in the name of purity".
I don’t know why that’s what you took from my pseudocode, since one of the examples maintained the exact same interface. Why would it be less valuable if hooks-consuming components are factories/HOCs?
> I already explained fairly well why you want hooks to be inline in render, as they need to handle side effects, they need to compose with each other and react to changing values, and therefore they need to constantly run to check for updates. It’s 100% how they are used by everyone.
And all of that is possible with the APIs I suggested.
> I hope you find time to actually look at what hooks do more closely and find the power behind them.
Please don’t condescend. I’m plenty familiar with the material.
> You can try and hide that fact behind more syntax, but it doesn’t change the fact that your inner function you defined is depending on an outer closure that is stateful, breaking your law of no functions that act differently when called with the same values.
It’s not about hiding statefulness, it’s about:
1. Isolating it: this is every letter in the SOLID principles.
2. Maintaining clearly identifiable interfaces that are clear in their boundaries and expected behavior.
In Clojure, a language that's pure FP by default with clearly defined APIs for mutable escape hatches, this is achieved by convention with @ prefixes for state and ! suffixes for dereference. In JS/TS, this is achieved by convention by separating state init from post-init behavior (my factory/HOC example), or by passing/wrapping state (see common redux patterns). Both have corresponding types that make clear what’s going on.
Shoving state and return values into the same space makes that impossible.
You really don't understand composition as it related to hooks, sorry, it's not condescension, you are arm-chairing something and it's obvious. I could provide a complex example for you and then re-write it in your example, and you'd see how hooks is far more elegant, and maybe I will, but to be honest there's a million examples on the web.
And algebraic effects are a FP concept, go look at OCaml which has been pioneering them, so your appeal to FP purity doesn't even hold.
> You really don't understand composition as it related to hooks, sorry, it's not condescension, you are arm-chairing something and it's obvious.
You’re being a jerk. You’re not in the least being open to the possibility that I understand the subject matter but have omitted exhaustive detail because I’m typing on a phone in comments on HN. But if there’s something you think I haven’t considered feel free to show a use case, and I’m happy to work with it to show an API I think improves on hooks but accounts for it. Let’s collaborate.
> I could provide a complex example for you and then re-write it in your example, and you'd see how hooks is far more elegant, and maybe I will, but to be honest there's a million examples on the web.
Please provide an example. Let’s have a conversation, not... this.
> And algebraic effects are a FP concept, go look at OCaml which has been pioneering them, so your appeal to FP purity doesn't even hold.
I didn’t appeal to FP purity at all. I explicitly appealed to impurity! I appealed to interfaces that embrace it and clearly delineate it.
You admitted yourself you've never really used them in practice.
I would never having simply read some docs on Rust's borrow checker having never used it, go to some discussion of experts online and nerdsnipe with a pithy "It's a bad interface" comment without having ever seriously tried it.
You're not being a jerk, just a jerk-off. You're talking out of your ass for fun and you admit you've never actually used it, and your poor example show it clearly.
I mean really, think about it. I was honestly interested in seeing if you had some unique insight but your example has no relation at all to hooks, it would only solve useState but has nothing to do with useEffect which is equally if not more important. You replaced the old React `this.setState` basically and not at all hooks, it's a syntax many have done before, that I had personally invented ~7 years ago well before hooks and used it on a small app, and which solves none of the problems they solve.
And now you want me to personally educate you?
At first I was going to just leave it at this conversation, but you know what, fine, here's your example:
function Component(props: { isActive: boolean }) {
const [query, setQuery] = useState('')
const queryDebounced = useDebouncedValue(query, 200)
const searchResults = useSearch(queryDebounced)
const results = useLastValueWhen(searchResults, !props.isActive)
return <>
<input onChange={e => setQuery(e.target.value)} />
{results.map(result => <ResultItem {...result} />)}
</>
}
// these two are easy to write out so leaving out
const useDebouncedValue = () => // debounce hook
const useLastValueWhen = (a, b) => // hook that retains last a when b == true
const useSearch = (query: string) => { // can be used elsewhere
return useFetch(`http://localhost/search?query=${query}`)
}
const useFetch = (url: string, args?: any) => {
const [state, setState] = useState([])
useEffect(() => {
let alive = true
fetch(url, args).then(res => {
if (alive) setState(res)
})
return () => {
alive = false
}
}, [url, args])
return state
}
Okay I looked and... I'm not sure why you think this example can't be accomplished with my alternate API. You can compose hooks the exact same way and use them in the outer constructor/HOC function. You can nest said functions to receive incoming props. I honestly don't want to spend the time explaining functions as values and scopes, unless you sincerely think that I'm missing a use case here.
I even looked again at my examples and I’m a little annoyed that I even bothered putting a string around my finger for this condescending exchange. I accounted for this case in my second example! Outer function takes props, hooks are a function that expand them. There’s your composition bud.
Do your part and come back with an example that does the same thing in your syntax.
Don't be lazy and fool yourself into thinking you have a point because you didn't write it out.
I know what problems will come up and I'd like to see how you try and solve them. In the best case you end up with a more verbose syntax for doing the same thing, and in the likely case it will be brittle because you'd have to manually link the instantiations and re-runs, manually ensure you're calling things in order, and may even have to manually dispose things.
Yea, it's definitely a workaround for not having algebraic effects, you can basically think of it as a yield.
Of course ideally the language would have it first class, it doesn't change the fact that they are immensely useful. Having composable stateful logic that always handles side-effects is incredibly useful, and it works really well within the immutable data / top down data flow model of React.
I'd love to hear the alternatives, as I spent years researching and working on many (Redux and FRP/reactions) and they're simply not as nice.
I agree with you that hooks are a menace and seem to have been introduced for the same reason - to make new developers think less. I think that backfired spectacularly, but that does not mean you should not learn it and also know why this is an anti-pattern. Unfortunately junior devs are very happy to jump on the hooks and then when you come in to fix the mess, it's just a poor show.
EXACTLY! This is mind boggling. And it’s such an easy thing to solve, by allowing components with hooks to return components that receive hooks data as props.
The problem with understanding what hooks do is that they’re basically an involuntary red pill. React and other libraries supporting function components have either a compiler or runtime fiction that your view is a function of input. Hooks force the reconciliation logic into your view, and turn your “function” into a constructor. Sure, you can reason about it. But now you can’t reason about your language primitives.
JSX being a macro-like compile time primitive is actually its biggest strength. It’s a data structure but its implementation is undefined, so JSX implementations are free to compile it to whatever is most suitable for its runtime. Including compiling it away to nothing.
“It’s just JS” can certainly do that but you need additional compile time signals.
I'm on the opposite side. I think JSX is a horror show.
We've been trying to remove view from logic since the 90s, and move towards declarative views with data binding. Keep your nook and crannies away from the presentation layer.
But all the industry wants is shoving PHP in the browser.
I think it's sad. We're making the UI layer always more complicated.
Let's say your UI changes, like a redesign... you can rewrite your component and probably need to change most of them.
If your UI was just dumb and declarative and the data was salt on top, changing the UI is trivial.
The UI should declare what the UI should look like, not describe how it should do it.
This is why, as an old timer, I much prefer the direction VueJS took.
People always talk about separation of HTML and JS is a separation of concerns, but it's really not in my experience. It's separation of technologies.
The JS has to grab DOM nodes by class or ID and is highly coupled to its structure anyway, so putting them together (as React does) seems far more logical in my opinion. Separating them into files and using string classes to look them up is far less easier to read and leads to runtime issues (since a typo isn't actually a code error but just silently doesn't work).
JSX is really not like PHP at all. PHP just writes out strings - JSX is all JavaScript objects. What you are describing is more akin to something like Handlebars or Angular templates. Plus, JSX is just sugar and is not required to use React if you prefer something like hyperscript (no compile step needed in that case).
> Let's say your UI changes, like a redesign... you can rewrite your component and probably need to change most of them.
This is exactly what React does especially for stateless components that purely consume data. The component declartively says this is the data I need. The component doesn't care how that data is retrieved, that's an implementation detail separate from React.
For stateful components, this is also achievable but requires a little engineering and some good architecture (this is IMO the hardest part of 'React' - and this isn't even a concern of React - since React doesn't prescribe any specific approach).
If you define all the functionality for a component well, you should be able to easily rewrite just the view (the return value of a fucntional component or of the render method for class components) without changing any of the existing functionality.
This can be done by defining your component's behaviors via hooks (custom or builtin). Those hooks should provide any functionality and data your view needs and exposes that to the component. If no new data or functionality is needed, then nothing in these hooks (and more broadly code external to the render/return value) changes.
All that changes is the return value of your component and any css you want.
If you change the view, yeah you might have to update some event handlers for buttons that are now inputs, but that's not a UI only change, thats changing functionality too.
I felt this way when I first saw React and JSX a few years ago. Mixing concerns seemed like a terrible idea that would lead to ruin. And then I tried it, and realized it’s wonderful actually - that most of the time, when I changed some HTML, I had to update the corresponding JS anyway, and usually in a separate file (which at the time was jQuery or Angular 1). React put those things in one place, and writing components felt like writing functions.
The worst PHP code I’ve seen is the kind that mixed database queries with presentation, and that was just bonkers. Just a really truly awful mess.
For me, that was the “PHP” I envisioned and what I didn’t want to rehash with React, and I can confidently say that has worked out pretty well.
JSX is entirely about removing view from logic. Templates/render pipelines all have their own logic depending on the data available to them. That’s all JSX does. The benefit of JSX vs the other solutions in its space is that it’s a data structure and it’s render-target agnostic, as well as rendering engine agnostic (so you can render the same structure to different outputs or in different ways for the same output).
Actually, JSX is the thing that keeps me from using React ;-)
I prefer the template syntax of .vue files or similar to the one riot.js uses. They use basically the same syntax html uses, just on a smaller scale and limited to a single component. That way you don't have to invent new was of embedding markup in code.
It's a view rendering library, but it also does state management while not actually doing it. Forgo just makes some design decisions that makes state management not require any code in the library.
So, it does what the react and react-dom packages do - it's not just the view.
Reminds me of mithril+jsx, only that mithril returns an object with a view() method instead of render() and has an auto-redraw system.
https://mithril.js.org/jsx.html
I've taken the example from your homepage and made it in React without using redux or hooks.
import React, { Component } from 'react'
class ManualStateComponent extends Component {
state = {
now: null
}
constructor(props) {
super(props)
}
// Since you don't want to call `setState`, here is a method with the name you prefer
rerender() {
this.setState({
now: Date.now()
})
}
}
// Example Component
class SimpleTimer extends ManualStateComponent {
constructor(props) {
super(props)
this.seconds = 0 // Just a plain variable
}
componentDidMount() {
setTimeout(() => {
this.seconds++;
this.rerender() // rerender!
}, 1000)
}
render() {
return <div>{this.seconds} secs have elapsed...</div>;
}
}
<script>
let seconds = 0;
setInterval(() => {
seconds++;
}, 1000);
</script>
<div>{seconds} secs have elapsed...</div>
Note that this deliberately functions slightly differently: it uses setInterval rather than repeated setTimeouts. This makes a much more accurate seconds counter, as setTimeout will wait for at least 1000 milliseconds before incrementing seconds and setting another timeout, meaning that you will always count time too slowly, commonly around 1% too slow, but more at times like if JavaScript execution is being throttled because the page is in the background. If you really wanted to do it the other way, it’d be something like this:
<script>
let seconds = 0;
function increment() {
seconds++;
setTimeout(increment, 1000);
}
setTimeout(increment, 1000);
</script>
<div>{seconds} secs have elapsed...</div>
The improvement to this sort of time-tracking is not setInterval vs setTimeout but rather "<div>{(Date.now()-startedAt)/1000} secs have elapsed</div>". That way your clock loses its dep on interval frequency.
Why use classes instead of functions. Isn’t the React ecosystem moving away from classes entirely? I remember hearing somewhere that a core maintainer of React stated we should be using function components everywhere now.
OP didn't want to use hooks, so I showed an example that doesn't require hooks.
I'm aware some of the React community use hooks, some of us still use classes. Classes provide a better interface when dealing with state that is shared between parent and child. This is a write up much better than I can explain: https://react-redux.js.org/api/hooks#stale-props-and-zombie-...
You're right - and I love what you said here. Backbone has been a huge influence for me. I liked everything being explicit, and the whole mechanism of your app working just as you designed it, without deviation.
Most developers don't understand Javascript, let alone React.
I recommend developers to look into history of React, why they chose to build it in first place and search for youtube videos explaining React concepts.
React is about data flows and managing state transitions with user experience.
Lot of these React replacement projects simply try to reinvent the wheel.
Reminds me of all the jQuery replacements that were attempted, but quickly realized that in order to support basic features, they had to recreate jQuery.
Yes, this is all spot on, though I'd say the majority of developers actually building real things do understand JS, and would have a look at this, and dismiss it for the reasons you stated.
What is the purpose for such libraries other than author(s) having cool entry in their CV?
These things don't create anything original and are only polluting already heavily polluted JS ecosystem.
When are we going to see actually something interesting?
Often a new thing, like react, has a very epic core idea, but the implementation leaks through, and/or it is interlaced with other ideas and concepts.
Projects like this are about replicating a core concept or an idea without the other stuff around it.
I don't have a good description for it yet, but I feel like I have seen this a couple of times now: a new i.e. product/tech/library comes out with one ore more interesting new concepts, and afterwards, over time, these core concepts get unbundled and isolated. From there, you can remix it with other ideas.
It's like the first wheel came on a car, then someone took the wheels off and played with them to invent the motorcycle.
Also that maybe was the shittiest made up example in history, but I hope you get the idea.
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.