Hacker News new | past | comments | ask | show | jobs | submit login
Crank.js – Write JSX-driven components with functions, promises and generators (crank.js.org)
205 points by migueloller on April 17, 2020 | hide | past | favorite | 101 comments



Here’s [1] a blog post introducing Crank.js. It goes into detail as to why the library exists. The main motivator for the creator seems to be that they really disliked the suspense API and wanted to offer a better way using JS generators and async/await.

[1] https://crank.js.org/blog/introducing-crank


Dan Abramov responded to this on r/javascript and mentioned that the rationale for Suspense wasn't so much to chase some academic concept of purity (and shoehorning caching in to the system to make it happen), but rather that Suspense was there to support a pre-existing need for a cache for cases like snappy back/forward routing.

Personally, I'm a bit on the side of Crank's author that I feel that the direction React has been taking wrt async support feels weird. Can't really put into words, but if I had to try, I'd say React's answer to async feels like something getting lost in translation. There are certainly interesting ideas being explored, for example, in Dan's blog post about algebraic effects[1]. But I feel that Crank's approach feels like a more elegant way of "translating" that concept into JS than the whole promise-throwing thing that React seems to be pursuing.

[1] https://overreacted.io/algebraic-effects-for-the-rest-of-us/


Wasn't one point also that generators and such are contagious?


async is contagious, generators are not.

    const foo = async () => {}
    const mustAlsoBeAsync = async () => console.log(await foo())

    const gen = function* () {
      let n = 10
      while (n) yield n--;
    }
    const doesntNeedToBeGenerator = () => {
      for (const n of gen()) console.log(n)
    }


I enjoyed reading this blog post, and the perspective that the author brings. The notion that a cache is a big ask is novel to me, but I think it is a good point that it does complicate things more for simpler cases.

However, I would like to hear the author's thoughts on Concurrent Mode in general, and how Crank.js addresses the same issues. There's no mention here of time slicing, which is what Concurrent Mode really unlocks. IIUC Suspense was discovered, not invented, after implementing React Fiber and exploring the knock-on effects of being able to restart rendering from a place in the component tree.

React's insistence on rendering being "pure" is not dogma; it is an attribute that unlocks the ability to pause and resume rendering at any node in the component tree... like if an important update from a keyboard press comes in that you need to handle right now. This means that when CPU is scarce, jank can be alleviated by prioritizing user input and other high priority updates.

There's a tweet out there by sebmarkbage that gives a slightly cryptic response to using generators, but the gist that I remember is that because generators are mutable, you don't get the properties you need to arbitrarily pause and resume a component just like it was when you paused rendering it.

The author claims that this is just dogma, but I believe (and the React team believes too) that time slicing provides a measurable benefit to the user experience. This point is contentious still; Rich Harris of Svelte fame claims that _doing less work_ is more important than prioritizing it. But the author here doesn't address this at all, instead claiming architectural simplicity is its own goal.

I'm excited to see alternatives to React come out and generate excitement, and I'm very interested to see where Crank.js continues to evolve. My hope is that in 5 years we'll know way more about all of these questions than we do now, and we'll be able to put those into practice in better frameworks, delivering better applications.


Hi, author here. Glad to hear that you enjoyed the blog post.

> I would like to hear the author's thoughts on Concurrent Mode in general, and how Crank.js addresses the same issues. There's no mention here of time slicing, which is what Concurrent Mode really unlocks

I think Concurrent Mode is a really interesting project, but I also think that the developer experience of using CM as the React team has defined it is really lacking.

There’s the issue of querying when a specific component (or the whole tree) has finished rendering. With React, rendering is treated as a black box, and this was mostly okay when rendering was synchronous, but people often ask with concurrent mode, how do I know when rendering has finished, locally or globally. The answer the React maintainers would give would be, probably, useEffect hooks, but that’s not enough for me.

Promises are like the Planck constant of asynchrony in javascript; they’re the smallest units of time possible for executing code later and their callbacks have the highest priority. To not have a promise-backed API for CM seems like a step backwards. Even if I were to implement time slicing, I would want it to be promise based. Also, React fiber time slicing comes at a cost, in the sense that you have to check the clock for every unit of work to make sure you don’t exceed your budget. I would want any sort of priority-based it to be done on an opt-in, limited basis. I definitely don’t think every component should be a possible asynchronous breakpoint, that sounds like a nightmare to debug.

I dunno, I need to write a longer, better structured blog post about this stuff, but here’s something to think about: if you want concurrent mode or time slicing or scheduling priorities, the tools to implement it in user space are probably already there with Crank.


I haven't used concurrent mode in anger yet, but I have used frameworks built on top of React that introduce their own async scheduler for rendering - Reagent, re-frame in ClojureScript-land.

You end up with two issues IME with async tasks (rendering being one of them):

- Making everything async - without prioritizations - means that at some point you have simply moved the problem into the scheduler. Just like taking a sequence of sync operations and wrapping them in a promise chain doesn't speed them up, likewise making every render async by itself doesn't speed things up at all (on the contrary it can make things slower).

- Using prioritized async tasks only allows you to prioritize the things which participate. If a component renders, or any other computation occurs, and isn't sent to the scheduler, it can steal work from higher priority things because the scheduler doesn't control it.

This is why IME a la carte async async rendering isn't super useful. It is a lot of mental overhead (and potentially laborious) to manage your application's schedule while also trying to build features. It would be interesting to have tools to tune it when necessary (which I assume React will provide in the form of ways to schedule computations at different priorities), but having it opt-in means that the default case is everything will steal work from the things that you are actually marking as high priority!


One thing I really want to make sure with Crank is that sync stays sync and async stays async. One of my early formative programming experiences was having to track down a race condition in an om app I was working on which was caused by the renderer making my sync code asynchronous.

https://github.com/omcljs/om/issues/173


As an outsider, I honestly feel that there's a strong difference in opinions between Dan and Sebastian. One doesn't need to reason too hard about the purity thing: Dan himself admits hooks were criticized (accurately) for being impure.

On the other end of the discussion, the claim about impurity preventing prioritization is very weird, because simply going async will shuffle the rest of your rendering further out in the microtask queue, giving the opportunity for event handlers to fire half way through a vdom render pass, effectively prioritizing user input.


The React team has been pretty clear that Concurrent Mode needs rendering to be pure, as render methods may get called multiple times but have the results thrown away if the render pass isn't completed and committed to the DOM.

The case of user input is particularly relevant here.

Say React starts a render pass for a relatively low-priority update. It walks through the component tree asking components to render themselves, runs out of time on this tick, and pauses the half-calculated render pass. While paused, the user types a key in a text input or clicks a button. That will be treated as a much higher priority update, so React sets aside the half-calculated render tree, does the complete render pass based on the input event, and commits it to the DOM. It can then "rebase" the half-completed low-pri render pass on top of the new tree that resulted from the input update.

If components go kicking off side effects while rendering, whether it be as simple as mutating a React ref object or more complex like an async request, those side effects will likely result in unwanted behavior because they might happen multiple times or happen at the wrong time.


Yes, I'm aware of the concurrent mode contract. But do you see the Stockholm syndrome here?

If I were to tell you that in order to use a hypothetical Floober framework, your code must be tail call optimizable or else the framework has undefined behavior, surely you would think the author of Floober framework was insane.

It seems strange, for example, to dismiss async functions on grounds of function coloring when the alternative is coloring functions with a conceptual requirement of "purity". Or to ignore the opportunity for a paradigm shift to something more naturally reactive (e.g. a la s.js) considering how there was already an API shift when going from OOP components to functional + hooks.


You’ll notice that in a lot of the Crank examples, I freely mix side-effects with the actual yielding/returning of elements. This is because Crank doesn’t treat side-effects as special. Given that rendering is a huge side-effect in and of itself, I always thought is was kind of unfair that React could peform them and the user couldn’t.

The reason this is possible is because Crank is executionally transparent. If you render a component, and that component calls refresh 4 times, the developer can know that the component has been updated 5 times. You get no such guarantees with React, and it will even go out of its way to make sure that you don’t put side-effects in your code by calling your components multiple times in development, which I think is crazy.


The whole React story is very interesting. Authors are extremely adamant about everything being pure, but React still doesn't solve an underlying issue that every frontend framework since the beginning of time solved. Which is automatic tracking of properties and re rendering efficiently(think Vue, Angular, Angular 2, Ember). IMO you need all these new optimization techniques crammed on top because it's so easy to shoot yourself in the foot with performance out of the box with React

Oh you didn't memoize a function, well you just re rendered a FlatList with 500 items in it on an Android phone that cost $0. You need an addon library such as Mobx to really unlock the power of React and make it sane enough to use.


"Authors are extremely adamant about everything being pure"

I had the excact opposite experience.

Frameworks like Cycle.js and Callbags are pure and try to emphasize that, but React always priorized practical solutions higher than FP concepts.


Except you don't need an addon library at all. You can construct efficient and performant components using just react state/hooks and props.


Yes, for sure you can get good performance out of the box. It just takes a lot more time than other frameworks. In my experience it's a constant moving state up and down. Spending an insane amount of time to put state in the correct spot so it will be efficient. The easiest thing is to just put all state/business logic in domain objects and subscribe the components to it. That's how you're used to thinking about business logic of an application anyway. It's easier to reason about and it performs much better. This whole useMemo, useCallback, is just comical. Let the computer do the work for you.


Weird, never had any of those issues. I've always had state near the top and used props to bring it down. Pure components (which is any function component) do not re-render unless their props change. I've built rather complex applications also in the logistics and financial forecasting verticals. We are talking many tables, charts, dashboards, etc...

In fact, I found the MobX approach with reactivity harder to control performance for because it's triggered by state changes. But when you trigger a lot of state updates it can be really hard to reason about performance because it's not obvious what components will update due to that change in state.


Tables, Charts and Dashboards sounds like a lot of displaying of data. Not updating state. Or when you are updating state (like date picker), you're updating the world. Just an assumption, sorry if I'm wrong. Web is usually fine to be honest, doesn't come up until you're pushing state on keypress. The Stripe onboarding page and Twitter create tweet UI both have these performance problems. My day to day is React Native which is where these problems crop up almost immediately and getting performance out of the box is a chore that, again, none of the other frameworks make you do. Computers are smart they can solve these problems for us.


Holy hell. I didn't realize this. This is nightmare fuel and the blog post you linked is leveling some really good criticism. My favorite part is where he linked to a Github issue where the React team instead of responding with a real answer just repeated their manta "Suspense will solve everything" and closed the issue. I know "open source devs don't owe you anything." But I've noticed that sentiment is now being used in a lot of open source projects, especially the well funded ones by big Corps, to just shutdown anything that derails whatever their underlying motivations are.

  I mostly abandoned the effort and any sort of greenfield React development, when I 
  came to understand what Suspense was and how unwieldy it would have been to 
  incorporate Suspense into the hooks I had written. As of April 2020, the mechanism 
  behind Suspense is for components which make async calls to throw a promise while 
  rendering to indicate that the component is doing something asynchronously. 
  “Throw” as in the way you would “throw” an error in JavaScript with the throw 
  operator. In short, React will attempt to render your components, and if a 
  thenable is thrown in the course of rendering, React will catch it in a special 
  parent component called Suspense, render a fallback if sufficient time has 
  elapsed, and then when the promise has fulfilled, attempt to render the component 
  again. I say “as of April 2020,” because the React team has consistently said the 
  exact details of the Suspense API might change, and has used this declaration to 
  preempt any possible criticisms of this mechanism


Well, I don't think this is fair, either. As someone points out in the thread before making some good points to OP, this topic is discussed ad nauseam in React's github issues and elsewhere.

There are many good discussions online about this and all the different solutions out there with different trade-offs. And the Suspense system is what the React team came up with to solve it.

I don't see how someone wanting to rehash the discussion requires any other answer than "we're solving this with X, it's just not yet available."

This is one of the most common trope of any sort of forum system, github issues included: everyone thinks they're the first person to broach a topic, yet everyone else has seen it 1000s times.


Really great origin story for either a JS framework or a Batman villain…


I don’t get the whole trend of jamming state in functions. hooks use closures for state and if you call hooks in wrong order you get the wrong values back. In this instance, this.refresh is magical.

One hard lesson I’ve learnt after doing 15 years of frontend is to avoid too much magic. As you build more complex things, it should be easy to bring in new devs and they should be able to simulate the system in their heads without expecting too many surprises. Or being laser diligent that a silly mistake could blow things up.

That’s what I like about original react. It was super duper simple idea. Classes are meant to have state. The state object is called state. Render function returns a view of state.

While generators are cool, all of this could be simply achieved with existing paradigms. That’s why I prefer preact over react. It’s way smaller than react, less bloated and equally fast.


While I tend to agree, IMHO your example is off - it is a "state" property because the class is newly instantiated each time a render is going on, and then the property is magically re-populated for you so you can still treat the class like a class, not because "classes are meant to have state" - you would use class properties directly as your state if that was the case. Hooks are exactly the same magic (the state property is populated the same way as the useState hook works), only accessed differently.


> the class is newly instantiated each time a render is going on

No, it's not? Where do you think the state goes, if the instance doesn't hold it?


It's stored internally in React. The instance of your component does not hold the state, just a reference to the state object which is repopulated each render (just like useState, it's the exact same mechanism), and after the render is done, the instance is destroyed.

More here: https://reactjs.org/blog/2015/12/18/react-components-element...


The instance of the class isn’t destroyed every render... you can keep other state on it too that is persisted between renders, changing it just won’t trigger a render.


You are right. I tried it here, for others: https://codesandbox.io/s/crazy-bush-cg2i0?file=/src/App.js


Sure that is the way it works now. Is that a guarantee of React though or implicit behavior that could change in the future?


It's just an implementation detail.


I don’t know how this library or React work, but you could pass the state as an argument to itself - then what GP said makes sense.


I’m not sure how Crank is more “magical” than any other framework. All `this.refresh` does is make the current component execute again. For function components, this means the component gets executed again, and for generator components, this means the generator instance gets stepped through again. I think once you get the hang of generator functions and how yield work it might sort of click what Crank is doing under the hood.


Isn't the point that it's less magical - stuff goes where you expect it and does what you expect, not where the framework magically and by convention places it?


I believe bikeshaving's point above is that you learn the semantics of generator functions in JavaScript and that's your 'magic convention' - one that's not unique to this library because it comes form the languagee


If the OP was saying that Crank is less magical then I agree. Although it always made me sad that we measure APIs based on how little magic they bring. Like go to Disney World or something.


What if there’s no class in the language?

The ‘original React’ as you mentioned for example, was modeled in standard ML. There’s no class in the language and it is okay to model the state in function.


Why is this important? Especially in regards to the author's point about keeping frontend development simple?


Because class is neither simple nor mandatory.

And people have to do massive meaningless things like swapping between class and function components every day, because of the introduction and elimination of states.


There was OOP in JavaScript before classes existed via prototype.


> hooks use closures for state and if you call hooks in wrong order you get the wrong values back.

Calling hooks in wrong order gets the wrong value back? Can you explain this one?


Hooks rely on a persistent call index. Here are a few posts that explain in more detail:

https://overreacted.io/why-do-hooks-rely-on-call-order/

https://github.com/reactjs/rfcs/pull/68#issuecomment-4393148...


But why would you even change the order? I don't understand how can this be an issue if you treat it as a static declaration which it simulates.


What if you wanted to return early in the component? E.g. a hook has returned a certain value and you now want to stop all other execution and return null. With hooks you can't as that would change the order.


Yeah but you also wouldn't try to return in the middle of function argument declarations. I know it's a little weird, but just treat it as a declaration block and you won't have any problems, what you're proposing is impossible with react architecture as a whole anyways (because the next component would get wrong state cells, it's a stack).


The annoying thing with function purity fanaticism is that it is out of touch with reality.

State. Exists.

State exists, so let’s deal with it with the tools that the language provides, instead of pretending it does not exist, just to realize later that it does and jamming as hoc answers to this initial oversight.

A UI component is fundamentally “something that generates values (view objects) asynchronously (over human-scale timeframes)”. That’s the definition of an async generator!


> The annoying thing with function purity fanaticism is that it is out of touch with reality. State. Exists.

It’s the opposite of all-state fanaticism, where nothing can be done or calculated without first 1. Creating classes, 2. Mutating their states, and often 3. Mutating global state.

And then... magic!

Both kinds of fanaticism are bad, and a middle-ground is probably best. Keep it simple. Use the best tool for the job, etc.

That said, having worked with lots of code deeply inflected with global state, I’m more lenient to accept function-purity fanaticism over all-state fanaticism because at least then you only need to understand this one local oddity, not how it depends on the entirety of the rest of the codebase.


I agree with that and I personally dislike OOP a lot and am much more on the FP side. But sometimes as you say a middle ground is best, especially when the thing is intrinsically stateful.

But I have to say Dan Abramov does a good job of explaining why they need that purity here and it makes sense https://www.reddit.com/r/javascript/comments/g1zj87/crankjs_...


I think the state monad in F# makes a lot of sense - I'm now on the functional side after (finally) understanding it.


Parent was arguing against putting state in functions when classes exist. They weren’t arguing against state full code.


Generators will likely be a new paradigm. They are too powerful for senior devs to ignore. And so the junior devs will be taught. Like promises were taught.


Generators are great and I love them. So are promises. Yes, they do take a while to understand the underlying mechanics of iterators but I wouldn't call generators per say magic.

The magic is "this.render". What is "this" in a function ? If we're storing any sort of state in this, then I expect to see "new <something>()", which creates a new this.

By default functions inherit the this as window, and it's frowned upon to use "this" in pure functions. Crank.js breaks the convention where "this" is magically something else. It will also make it hard to typescript type the function.

So now suppose you have mouse event handlers and key event handlers, when their functions get called, their "this" is something else and you have to bind it via either arrow functions or .bind(this).

So my point is, because of unconventional "this", as a developer I have to keep track in my head what this refers to inside a function because it's implicit. If I am working with a team, I have to carefully explain that "this" is unlike what you think it is, it's magically injected.

In javascript "this" is the source of many bugs, because it can be different things based on context. https://www.javascripttutorial.net/javascript-this/


Wait that doesn't make sense, was generators in the language before Promises, actually isn't await based on generators? How is that a new paradigm?


Promises and generators were ES6, so around the same time. Although people were using promises long before it became an official feature in ES6. I remember using promises as far back as 2012 or earlier - jquery had them. ES6 was 2015.

Await and async came after in ES7 and can be considered to use yield[1]. So there you go - that's a perfect example of the power of generators. Promises used to be more difficult until they were simplified using generators.

[1] https://stackoverflow.com/questions/36196608/difference-betw...


I prefer to avoid them. Debugging can be way harder. Remember good old days when you could understand minified JS relatively easily? Look at webpack bundle + babel with generators. Horror to work with.

Yeah, I know sourcemaps and all that, but even sourcemaps workflow is impacted; setting breakpoints can be weird, debuggers are still not perfect.


I think you are mixing up concerns. Webpack + babel does make debugging harder in general. Because you are looking at the output of a compiler.

But what does that have to do with generators? Do you have a specific concern about how Babel "compiles" generator functions using switch? Again, I tend not to look at compiled code, but I think the babel output for a generator function is easy to read.


> Classes are meant to have state. The state object is called state.

Except that's not how React works at all. There's no state in the class that lives between renders, it only seems that way due to... magic. The class gets destroyed and created between each render and the state object is automagically recreated with the previous state. You can complain about Hooks but saying they are magical and React classes are not is just wrong.


React classes don’t get destroyed and recreated each render. You can throw a console.log in the constructor, or save variables locally to the class, and see this for yourself.


Set `this.foobar` to something in a React class and you’ll see it persist between renders.


Fully on board with this newer trend (Svelte also comes to mind) of using only native JavaScript language features to accomplish what other frameworks do using abstractions. It seems like the natural next step as JS matures, as it’s my opinion that old frameworks that made up for JS’s shortcomings by abstracting them away and designing around the language’s limitations are intrinsically unable to natively and sensibly integrate new native language constructs after the fact without somehow changing the core framework itself (e.g. Suspense to integrate async/await in React).

Angular and Ember eventually gave way to Vue and React once dealing with the framework over the language became too cumbersome, and I would imagine Vue and React will eventually give way to “lighter” frameworks once they too become too bloated.


> Fully on board with this newer trend (Svelte also comes to mind) of using only native JavaScript language features to accomplish what other frameworks do using abstractions.

Really weird to ascribe that to Svelte, given that it has its own compiler that introduces non-Javascript syntax...


You’re right it’s not a pure example, but this quote from its website is what makes me differentiate from React/Vue:

> So we took a step back and asked ourselves what kind of API would work for us... and realised that the best API is no API at all. We can just use the language. Updating some count value — and all the things that depend on it — should be as simple as `count += 1` over `count.setState(count + 1)`


I would like to demo a simpler alternative to Vue and React: manipulate DOM directly. I do it in this application:

https://github.com/Rajeev-K/eureka

I find myself more productive coding directly against DOM APIs, using these tiny libs:

MVC router (500 lines): https://github.com/Rajeev-K/mvc-router/

JSX templating (200 lines): https://github.com/wisercoder/uibuilder


I’ve been passively looking for a better way to build the homepage for ChronVer and these links are right up my alley.

Thanks!


Agree. React started out simple, but now is over-complicated. Here's another tiny lib to use JSX in a native JavaScript application.

https://github.com/wisercoder/uibuilder

Uibuilder is "close to the metal", so there is no heavy library--especially one that completely changes the programming model of the web--sitting between your code and the browser


Can you elaborate on what makes React over-complicated?


In a way I feel it has become less complicated. I can do so much with hooks so easily using a dead simple API, for example. I write less code now than I ever did with react.

Admittedly, react now comes with that API and the previous ways of doing things, and that could be seen as over complication.


How are React and Vue grouped together in that regard? More specifically, how is Vue less of a framework abstraction than Ember or Angular?


If I remember correctly, React and Vue started off as more view-layers than anything else, and popularized the idea of 1-way, isolated components. Angular and Ember had always been all-in-one-heavy frameworks with 2-way bindings by default, and “partials” similar to components but state was intrinsically shared in a MVC paradigm.


Vue in itself is just like React, a rendering library with reactivity.

Vue also provides official batteries like Vuex and VueRouter but you do not really need those to use Vue.


From my enterprise seat, Angular is pretty much everywhere, including greenfield projects.


One more reason to stay away from enterprise seats..


This is spot on. I've been slowly switching from class based React to hooks based with Suspense, and a couple of things have become super clear.

1) hooks are magical, finicky, abstraction that don't play well with promises. They save code at a cost of understandability, subtlety, and new rules. I wrote a ton of Perl, and fundamentally believe that 1 line is better than 2 lines, weirdly hooks are making me rethink that belief.

2) Throwing promises is super tricky... I just wrote a class based component to cache the results of a hook, so I could wrap it into a function that would throw. I am not proud. 5 layers of abstraction needed where the same thing in a class based structure would be 2.

3) The hard part of UI is and always will be state. React (initially) got it mostly right. Redux was a detour, and this seems a much better way forward.

Best of luck with this, I'll be rooting for you.


I love this. Vjeux, seemed excited about it, which gives the project some immediate credibility IMO.

https://twitter.com/Vjeux/status/1250687160237211649

I've been using hooks since they came out and doing things with generators and async looks to be more intuitive to me. Instead of a language on top of a language, you just use JS, which has always been what made React great.

I also now have a reason to look for more places to use generators :) I didn't realize they could be so helpful


Interestingly, the thread you linked to also has links to other discussions where the React.js team provide their reasoning for why they decided against using generators[0] that the author of Crank.js didn't mention in their post. There's also a more basic breakdown of that explanation deeper in the twitter thread here[1]. As I understand it (and I am no expert), Crank.js's usage of generators is not the most performant because it doesn't provide an avenue to avoid recalcing work. In this case, memoization/caching does nothing to save you.

There are also other interesting tradeoffs, like suddenly making everything a generator as a result (as seen here). It's not that big of a deal to me, personally, but it's not something that everyone in the JS world is necessarily used to either.

Overall, it seems to be about target audience and priorities. Facebook seems to really care about intense performance at scale and they want all of their developers to be able to use their framework with ease. It seems they don't mind if that means requiring a bit of their own 'dogmatic' education as the author put it. Regardless, I agree. This is really interesting stuff!

[0]https://github.com/facebook/react/issues/7942#issuecomment-2... [1]https://twitter.com/swyx/status/1244749594472243200 (see @samselikoff's reply and following discussion)


I really like the way redux-sagas uses generators to represent side effects a values that get interpreted by the redux middleware they provide.


This is the comment I’ve been looking for.

I’ve been writing exclusively React for 3 years and the majority of react community are ill informed as to the origins of React.

Originally an OCAML implementation, there are clear benefits from pure functions, immutable data structures, and representing views declaratively in a DAG. I am simply restating the motivations and characteristics of React.

Then Redux comes long, inspired by Elm and it’s immutable update cycle, and the React community proceeds to spend the next two years utterly confused as to why anyone would want to use Redux. Followed by an onslaught of “You might not need redux” and “How I built my react app without redux” articles by shortsighted developers who merely reimplement Redux. :facepalm:

Redux-saga is the desired solution to handling async rendering while also staying true to the core properties of React. I’m glad you mentioned it in your comment.

I find the React community to be largely defined by its misunderstanding the core design and goals of React.

I find Crank an interesting and clever application of async generators.

However claiming that the core properties principles of React - functional properties, DAGs, and priority scheduling - are “dogma” is a shameful misunderstanding.

The correct criticism is of the behavior of the React team in their community response and the Suspense API alone - rather than the intrinsic properties of functional user interfaces.

Nonetheless, I am impressed by the technical effort in creating a new async generator API for rendering JSX. This project is no easy undertaking. Sincerely hoping for its future success (and self reflection).


I'm thrilled to see this, because I have been experimenting with using async functions that directly render their own DOM trees in between awaits, and I recognize a lot of the sentiment in the "Introducing" blog post, but I didn't have the insight of using generator yields with DOM trees, instead I had async functions that did this.draw(tree).

I had a different way of dealing with events. Basically I was seeing an app as a process hierarchy of self-drawing widgets that communicated via channels. I ended up wanting to make my own language to get decent pattern matching and the possibility of preemptible processes...

And for example instead of the Timer example which uses this.refresh in Crank, my version would have something like:

    let s = 0
    while (true) {
      this.draw(<div>{s}</div>)
      await sleep(1)
      ++s
    }
Then to deal with buttons, something kind of like:

    let i = 0
    let increase = channel("+")
    let decrease = channel("-")
    while (true) {
      this.draw(
        <div>
          {i}
          <button onclick={increase}>
            Increase
          </button>
          <button onclick={decrease}>
            Decrease
          </button>
        </div>
      )
      switch (await pick([increase, decrease])) {
        case "+": ++i; break
        case "-": --i; break
      }
    }


I'm learning Ember at work and there's a lot of "plain" JS floating around (async / await, generators).

One thing I've really missed is JSX. I wasn't a fan when I first heard of it in... 2014?... but I went in on it and it really does make sense and save time IMO. I've been playing with Mithril after a post here the other day and now I'm going to add this to my list to toy around with!


Okay this is very interesting, I love React but I'm also worried about the "complexity explosion" that is going on. As an example look at this "zombie children" article that has bitten me when trying to build a library:

https://kaihao.dev/posts/Stale-props-and-zombie-children-in-...

Curious about Crank.js, would this timer be valid as well (it works on codesandbox)? How does it stop/unmount?

    const delay = t => new Promise(ok => setTimeout(ok, t));

    async function* Timer() {
      let seconds = 0;
      while (true) {
        await delay(1000);
        seconds++;
        yield <div>Seconds: {seconds}</div>;
      }
    }


Hi! Author here, thanks for the interest.

Your example will just work! The only thing to worry about when using `while (true)` with async generator components, is that Crank will continuously pull values from the generator even if the renderer or a parent isn’t updating it, so if you forget to await something you’ll end up entering an infinite loop (which starves the microtask queue so it’s even worse than regular infinite loops).

> How does it stop/unmount? Generators have this nifty feature where you can stop or `return` it from the outside. What this does is it resumes your generator at the most recent yield, but rather than continuing execution, it “returns” the generator at the point of execution. In other words, it’s almost like it turns your yield into a return statement. This is what breaks out of the `while (true)` and prevents your code from continuing to run when unmounted. The cool thing is that you can also then wrap the loop in a try/finally block, and execute some extra code when the generator is unmounted.

If you have further questions let me know!


I didn't know you could forcefully end a generator from outside. IMHO generators are overkill 99% of the times. This is one of the biggest issues I had with the PREVIOUS Koa.js' documentation[1] and why I decided to launch Server.js with plain async instead of trying Koa. I had been doing JS for ~6 years by then and I could not tell how the simple "hello world" worked.

[1] https://koajs.com/

[2] https://serverjs.io/


I prefer LitElement; no reinventing of the wheel like JSX etc. do and no build required. Plus you get browser supported CSS scoping and life cycle support.


I didn't go full LitElement because I didn't want to add build steps (I'm waiting till they figure out ES6 module path resolution) but I found that the templating/rendering library LitHTML is great and easy to seamlessly and incrementally integrate in any webapp. It's just plain html, it's very fast and it just works. Javascript literal templates are a very elegant solution, turning strings and statements into lists of objects which can be efficiently cached, diffed and recursively traversed a la Lisp. To me JSX felt like a crutch to compensate for browsers being behind the curve. We no longer need a second html.


This is what I wanted React hooks to be like. They are a huge hack and it shows every time you need to call on conditionally. But I guess if React never comes around to this generator idea I’ll never get to use this, because we rely on many React libraries... Maybe crank could support compatibility with React components somehow?


What would be more likely is to use a codemod[0] to convert react libraries into crank, or close enough that only a few further manual changes are needed.

[0] https://github.com/facebook/codemod/blob/master/README.md


It's great to see people experimenting with different tradeoffs. One thing I wanted to mention is that I don't believe the author's claim that React sees purity as an end goal is accurate. I wrote my thoughts here, hope they're of some use: https://www.reddit.com/r/javascript/comments/g1zj87/crankjs_...


This looks great. I really like the idea of component changes just being a series of yielded updates. Really tidy API too.


The way the generators appear to work reminds me a lot of Concur: https://github.com/ajnsit/concur-documentation

But controlling or understanding the control flow in Crank generator components, ie. when does ‘yield‘ return, looks like a bit of a nightmare!


Which part looks like a nightmare to you?

I wasn’t quiet sure what you mean by “when does yield return”?


“Nightmare” is too strong, but it looks from the simple example that generators are managed by jumping between coroutines, and one of the coroutines is the renderer? (Sorry, I’m on my phone and probably not conveying my point very well)

I’d be interested in seeing this hooked up to Xstate.


I tried to make a library as a workaround with async treatment in React which I also don't like, but besides it being a library (already a -1) it also didn't allow you to have Hooks inside these async components, which was a showstopper for me. But still a fun experiment! This is how it worked:

    // Books.js
    import React from 'react';
    import pray from 'pray';
     
    // Wrap the async component in pray:
    export default pray(async () => {
      const books = await fetch('/books').then(res => res.json());
     
      return (
        <ul>
          {books.map(book => <li>{book.title}</li>)}
        </ul>
      )
    });
Would love to see good support for Async in React without the complexity that Suspense seems like it's going to bring.


This is terrific! I have always wondered why people don’t use async generators more as they are the perfect fit for react like use cases: what is a react component if not “something” that generates new values asynchronously over time?

I really hope this idea makes it into the mainstream react community and even blend with it. Let’s call this react 2.0 and start building on it, while keeping it compatible with react 1.x for now!


This looks really interesting. I love the use of generators! Currently using redux saga so something like this would tie in very nicely.


This looks very interesting but I have one concern: Wouldn't code where the "view" part and application logic are tightly coupled be way harder to test than an architecture that renders the view in a pure function and does state management somewhere outside entirely? (Which is not what React is today but might correspond to the original idea.)


The author does not mention solidjs (https://github.com/ryansolid/solid) in his apology for yet another js component library blog post. From a cursory glance, solidjs & crank.js seem similar in motivation & api...


I love the blog post. Great to see some people out there that are still questioning the status quo and not simply giving in to appeals of authority. The author saw things they disliked about React and didn't let the hand-wavy reasoning silence those criticisms.

Well done


I'd love to be able to use this with just a browser side script tag and no build step.


It looks like surplus which also support jsx/tsx. Both of them compose a dom element as oppo to vdom object


this is amazing and just what i’ve been looking for. thanks my man!


Are there any benchmarks somewhere?


As I understand it, this is extremely early and shouldn't be used in production, so there are no benchmarks.

What's interesting are the ideas of using native JS constructs where React opted for implementing things like hooks, refs, suspense, etc.


When I read that bit about "No [hooks] are needed" I did a double-take. IMO once you get the hang of hooks, React suddenly starts to look somewhat sensible… but anyway the Crank sample code looks promising.




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

Search: