
Crank.js – Write JSX-driven components with functions, promises and generators - migueloller
https://crank.js.org/
======
migueloller
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](https://crank.js.org/blog/introducing-crank)

~~~
lilactown
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.

~~~
lhorie
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.

~~~
acemarke
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.

~~~
lhorie
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.

------
nojvek
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.

~~~
tchaffee
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.

~~~
akritrime
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?

~~~
tchaffee
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...](https://stackoverflow.com/questions/36196608/difference-between-
async-await-and-es6-yield-with-generators)

------
thebradbain
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.

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

~~~
thebradbain
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.

------
speajus
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.

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

[https://twitter.com/Vjeux/status/1250687160237211649](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

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

~~~
Zezima
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).

------
mbrock
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
          }
        }

------
leetrout
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!

------
franciscop
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-...](https://kaihao.dev/posts/Stale-props-and-zombie-children-in-Redux)

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>;
          }
        }

~~~
bikeshaving
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!

~~~
franciscop
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/](https://koajs.com/)

[2] [https://serverjs.io/](https://serverjs.io/)

------
difosfor
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.

~~~
BenoitEssiambre
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.

------
felixfbecker
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?

~~~
mattigames
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](https://github.com/facebook/codemod/blob/master/README.md)

------
danabramov
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_...](https://www.reddit.com/r/javascript/comments/g1zj87/crankjs_an_alternative_to_reactjs_with_built_in/fnkkgg3/)

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

------
bodhi
The way the generators appear to work reminds me a lot of Concur:
[https://github.com/ajnsit/concur-
documentation](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!

~~~
leetrout
Which part looks like a nightmare to you?

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

~~~
bodhi
“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.

------
franciscop
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.

------
crubier
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!

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

------
t0astbread
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.)

------
briantakita
The author does not mention solidjs
([https://github.com/ryansolid/solid](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...

------
MatthewPhillips
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

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

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

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

------
pier25
Are there any benchmarks somewhere?

~~~
migueloller
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.

~~~
Eric_WVGG
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.

