
Show HN: 33 line React - leontrolski
https://leontrolski.github.io/33-line-react.html
======
pomber
Nice article, short and to the point.

If anyone wants to take this idea to another level, I wrote a post showing how
to do the same but in 10 times more lines of code: [https://pomb.us/build-
your-own-react/](https://pomb.us/build-your-own-react/)

~~~
aenario
Love the format with "live-coding". Is it git-based? Did you open source it?

~~~
pomber
the blog source:
[https://github.com/pomber/site](https://github.com/pomber/site)

the post source: [https://github.com/pomber/site/blob/master/posts/build-
your-...](https://github.com/pomber/site/blob/master/posts/build-your-own-
react/index.mdx)

~~~
capableweb
Very cool, thanks for sharing that! Now all that's missing is a "eval" button
that runs the result and drops you in a repl to experiment with the results.

~~~
porsager
Hey, that's what Flems is for. Check out
[https://github.com/porsager/flems](https://github.com/porsager/flems) \- a
single include embeddable sandbox (coincidentally written in mithril as the
original post references). It's also used for
[https://flems.io](https://flems.io)

~~~
capableweb
Hm, you seem a little to eager to promote your own stuff. This thread was
about the "live-coding" aspect where the code updates on the left as you
scroll down the post, so you can follow along with how the code is being
built. I fail to see how your project does that. Yours seem like the standard
code playground, so very different from what was linked earlier in the thread.

Or I misunderstand or simply missed where the "live-coding together with text"
comes in.

~~~
porsager
Hehe, you’re probably right about that, but you could script the live part
with the api, and then get the eval part to update as you mentioned, and allow
the user to play around at the same time. I'm sorry you didn't find it
related.

~~~
capableweb
No harm done, just a few bytes stored on HNs servers somewhere so no worries
:)

Yeah, I guess using the implementation linked before + your solution would
create what I wanted, so thanks anyways for sharing. Never know when someone
might find it useful.

------
anonytrary
This library isn't meant to have full parity with React, but it's a great
example that illustrates how simple a naive VDOM diff/patch is to implement.

That said, this is missing a lot of essential optimizations that libraries
like React make. For example, key and type matching, batching updates,
scheduling updates, and other stuff that isn't immediately apparent without
reading the code.

~~~
leontrolski
I'm not sure if any of their content is still relevant, but mithril makes some
bold claims for >React performance: [https://mithril.js.org/framework-
comparison.html#react](https://mithril.js.org/framework-comparison.html#react)

"Generally speaking, React's approach to performance is to engineer relatively
complex solutions.

Mithril follows the less-is-more school of thought. It has a substantially
smaller, aggressively optimized codebase. The rationale is that a small
codebase is easier to audit and optimize, and ultimately results in less code
being run."

(Obviously, I'm not claiming the same for this toy example - just that
batching updates etc, may not be necessary for performance).

~~~
sdegutis
I wondered what happened to .prop() which looked like it had a lot of
potential. Looks like that potential was (at least partially?) realized as it
evolved into a "stream" library [1], which basically acts like cells in a
spreadsheet. This seems like a really useful concept which could be used to
create more kinds of UI frameworks than just declarative React style ones.

[1]: [https://mithril.js.org/stream.html](https://mithril.js.org/stream.html)

~~~
modarts
[https://cycle.js.org/](https://cycle.js.org/)

~~~
chrisweekly
+1 for cyclejs! Strange to me that it hasn't caught on more

~~~
sdegutis
I just looked over their website for 10 minutes and still can't figure out how
it handles more complex nested components with complex state. Is it like Elm
and Redux where it has a giant tree and you have to update the root with a
bunch of events, creating intermediate adaptor events from the deepest nodes
all the way to the top? Does it have anything like React's Contexts? I really
like the examples I saw on the landing page, but I would rather like some
comparisons of how to do the exact real-world things React can do. Mithril's
website did this pretty well. Maybe this is hurting adoption? Or maybe it's
just me.

------
leontrolski
Author here, nice to see this got some interest! As a very small benchmark
(uh-oh!), in a console on the linked page, I tried to make/recreate 10,000
divs:

    
    
      const root = document.getElementById('noughts')
      m.render(root, {children: [...Array(10000)].map(_=>m('', {class: ['hi']}, 'hi'))})
      m.render(root, {children: [...Array(10000)].map(_=>m('', {class: ['bye']}, 'bye'))})
    

wrapped with:

    
    
      console.time('a');  ... console.timeEnd('a')
    

On my laptop (admittedly a fairly new macbook), I got (approx):

    
    
      130ms to make divs from scratch
      80ms to switch from 'hi'->'bye'
      50ms to re-render with no changes
    

I'd be super interested as to what comparable figures would be for React.
There's a fair bit of interesting discussion below surrounding performance,
but with no datapoints.

~~~
jsf01
There are some fairly standard vdom benchmarks you could implement instead.

[https://github.com/krausest/js-framework-
benchmark](https://github.com/krausest/js-framework-benchmark)
[https://localvoid.github.io/uibench/](https://localvoid.github.io/uibench/)

Or even just something following mithril’s own benchmark suite:
[https://github.com/MithrilJS/mithril.js/blob/next/performanc...](https://github.com/MithrilJS/mithril.js/blob/next/performance/test-
perf.js)

~~~
leontrolski
thanks!

------
red_admiral
What I really like about this is it removes some of the feeling of magic
around react and similar frameworks. Yes there's a lot more going on in real
react, but the core idea is simple and you can build your own and really learn
what's going on at the same time. Well done! Bookmarked!

------
leontrolski
Author here, this was written as a way for me to explore the core idea of
react in a minimal setting. Things it's missing

\- jsx

\- routing

\- performance (maybe?)

\- component state management stuff

What else would you include?

~~~
pgt
Exclude JSX and routing. You'll need some state. Routing != rendering and JSX
is an abomination.

For optimal performance, look into differential dataflow:
[https://github.com/TimelyDataflow/differential-
dataflow/issu...](https://github.com/TimelyDataflow/differential-
dataflow/issues)

~~~
dahart
This looks interesting! Not a working replacement for React, right? I would
probably add that for optimal performance, don’t use functional differential
rendering at all. Keep state and use direct DOM manipulation of only elements
that change, right? The very idea of re-rendering by throwing a whole new page
up and letting the system diff and update is guaranteed not to be optimal
performance, it’s always doing more work than necessary. The tradeoff is that
the optimally performant solutions are imperative and stateful and involve all
the difficult and bug-prone techniques everyone’s trying to avoid. Engineering
for functionality is a solved problem. Engineering for performance is not.

------
jonahx
Very nicely done. For a similar idea that can be used in production as a React
replacement, check out the Meiosis pattern:

[http://meiosis.js.org/](http://meiosis.js.org/)

It has most of React's benefits, while being extremely lightweight and fully
grokkable (no hidden parts, no magic, no framework logic) to any developer.

~~~
benbristow
With none of the community support or third-party components that the React
ecosystem has?

~~~
keb_
Why use _any_ new & upcoming tool when an older, more popular tool inevitably
has more community support and third-party components?

------
codegladiator
Looks like mithril (the m object and m.render almost the same) ?

[https://mithril.js.org/](https://mithril.js.org/)

~~~
leontrolski
Yeah, it's _very_ mithril influenced (I love that library) - I namecheck them
in the article at the top.

~~~
codegladiator
Oh I completely skipped that line. I like mithril a lot as well but haven't
been able too use it in a real project.

------
ppeetteerr
> const move = (value, i, j){...}

> This function makes a move in the game, it takes 'x' or 'o' along with 2
> integer coordinates. It will mutate all the state variables to reflect the
> new state of the game. After that, it calls renderNoughts(), this is a call
> to rerender the game - but we'll come back to that.

Dear programmers, not related to the actual article but, when you have to
describe your function with a paragraph of text, please consider improving the
naming of your function and its parameters.

~~~
bigmanwalter
It seems perfectly well named to me! I love when code bases have more lines of
comments than actual code.

~~~
ricardobeat
Sometimes the comments are _only_ required because the code is confusing.

In this case the function both mutates the state, and renders the app. If
using a framework like React/Vue etc it might make perfect sense to call this
function `move`, but here it has a lot more responsibility and would need a
more representative name like update/setState/renderApp/etc. But I think it's
pointless to debate as it breaks the single-responsibility principle and
you're unlikely to find something like this in a large scale app anyway.

~~~
bigmanwalter
It could also be because the code is terse. In that case it makes sense to
explain what's going on.

------
seanwilson
I'm guessing diffing and updating the DOM is really inefficient if there's a
lot of DOM elements and a few frequently updating part of your UI?

e.g. if you added a clock to the game in the article that updates every few
fractions of a section to show something like "14.3 seconds taken so far".

The solution to this would be to make the clock into its own tiny self-
contained component and to make diffing smarter so that only the DOM parts
that belong to the clock need to be diffed/checked each clock update?

~~~
robertakarobin
You would think that diffing is inefficient, but apparently modifying the DOM
is significantly more inefficient. Mithril -- the framework that inspired this
example -- has an interesting take:
[https://mithril.js.org/vnodes.html](https://mithril.js.org/vnodes.html)

~~~
websitescenes
I think he means that this snippet of code doesn’t account for diffing like
React does and would fail if scaled out. I think the OP is aware of that
though.

~~~
TheRealPomax
As a 33 line implementation "just for fun" exercise, no need to guess: this
was not meant to be _actual_ React in fewer lines of code =)

------
zserge
Nice project! I've also recently made a React clone in <1KB, with JSX syntax
and Hooks - [https://github.com/zserge/o](https://github.com/zserge/o)

------
jmchuster
If you came to this comments section looking for an actual lightweight
standalone version of react, preact+htm works wonders.

~~~
lioeters
I searched for "preact" in the thread, since I'm a big fan. Its small size and
compatibility with React API make it suitable for use in "embedded widget"
type situations, where a dependency on React would be too heavy. It's also
perfectly suited for building full apps, or static sites. I've been using
Preact on various projects for years, I love it.

------
k__
Nice experiment.

I've also "re-created React" multiple times before it was actually created.

Diffing somehow always ended up being thr most natural way tondo things in the
end.

My selling point in the end were the components

~~~
leontrolski
My feeling is that if html and js had _syntactically_ shared data structures
at their conception, we wouldn't have quite so much frontend mess as we do now
and declarative UI would have been the norm for a while. Having said that, I'm
not sure how much precedent this has in UI frameworks outside of the web.

~~~
k__
You think so?

What would be different if

    
    
        Document.createElement('a')
    

Would look like

    
    
        <a/>
    

?

The rest would still be imperative code.

~~~
ken
I don't interpret "html and js had syntactically shared data structures" to
mean what you wrote, unless you also replace all other JS data structures
(arrays and dicts and ...) with <> syntax. You've made it more concise but
it's still distinct.

For an example of a system where the programming language and document
generator share data structures, look at Hiccup (and Reagent). One of their
very first examples is putting a loop inside a list. There's no special API
for the attributes -- it's just an ordinary map. There's no special API for
the children -- it's just an ordinary vector.

------
sfvisser
Nice! Explorations like this are truly useful for understanding and maybe even
for innovation. You might figure out new ways of expressing things that are
unlike react, but solve particular problems better.

As a next step I'd recommend trying to add component-local state connected to
the component's lifetime. This is essential when building larger applications
out of abstract building blocks that hide inner workings. I expect this to be
a nontrivial addition, but I might be wrong!

~~~
leontrolski
I'm not sure I can quite express what I'm thinking, but do you think this
could be achieved orthoganally to the rendering library itself? (Just by
"normal" js).

------
joosters
This is probably a naive question, coming from someone who has written very
little JavaScript:

Why do we need React? Why can’t the browser do the same job? Instead of
passing a virtual dom to react, and letting it ‘diff/patch’ the real dom, why
can’t code regenerate the dom directly, in a similar way? This would seem to
save both time, memory and effort, since the browser can do the diff/patch
itself, much more efficiently?

~~~
pier25
I've been saying this for years.

If reactive data and data binding were solved at the browser level we'd save
so many kBs and CPU cycles.

~~~
efdee
If my cat was a cow, she'd give me milk. The hard part is turning my cat into
a cow.

~~~
Silhouette
I'm not sure this is a great argument. Web standards and browser
implementations evolve partly through absorbing ideas that lots of people are
using anyway. A decade ago, no-one was using CSS variables or HTML media
elements, but lots of people were using CSS preprocessors and Flash video
players.

~~~
efdee
But most people are -still- using CSS preprocessors today...

------
peey
It seems like `m.update` function queries the current DOM state.

I'm interested in the performance implications of this. From what I know,
other vdom libraries maintain the current DOM state in-memory for efficient
computation of what should be updated.

No doubt that this is much simpler and ought to be at least more efficient
than re-drawing everything. How much performance do you lose due to querying
the DOM state?

------
manthideaal
In the source code to perform el.classList = v.classes the author uses:

    
    
      for (const name of v.classes)
        if (!el.classList.contains(name)) el.classList.add(name)
      for (const name of el.classList) 
        if (!v.classes.includes(name)) el.classList.remove(name)
    

Why the standard decided to make classList read-only?

Could ele.className=(v.classes).join(" ") be a valid and performant solution?,
perhaps is to avoid the string to token traslation for performance reasons?,
then why don't they include a classList.set method?

~~~
leontrolski
Agreed, let me know if you find a better way! I feel if the browser APIs were
slightly more declaratively inclined, this article could be "10 line React".

~~~
manthideaal
to merge the properties of two objects you could use

    
    
      let merged = {...obj1, ...obj2};  See (1) which has 2808 votes.
    

(1) [https://stackoverflow.com/questions/171251/how-can-i-
merge-p...](https://stackoverflow.com/questions/171251/how-can-i-merge-
properties-of-two-javascript-objects-dynamically)

------
uk_programmer
I just had a look at the dom implementation and it similar to what I have done
in typescript. There is only so many ways you can skin a cat.

If you are wishing to do something similar yourself it is worth looking at
hyperscript source code:

[https://github.com/hyperhype/hyperscript](https://github.com/hyperhype/hyperscript)

------
diablo1
This reminds my of Cash[0], a lightweight jQuery alternative for working with
the DOM in a more accessible way

[0] [https://github.com/kenwheeler/cash](https://github.com/kenwheeler/cash)

~~~
leeoniya
Cash has some api incompatibilities with jQuery.

[https://umbrellajs.com/](https://umbrellajs.com/) is both smaller and better.

------
jxub
Weirdly triggered by the function arrows lacking spaces around them. Good work
though

------
CitrusFruits
Anyone who likes the idea of a minimal virtual DOM (like this) will probably
appreciate MaquetteJS: [https://maquettejs.org/](https://maquettejs.org/)

------
leontrolski
Further musings here -
[https://news.ycombinator.com/item?id=22791473](https://news.ycombinator.com/item?id=22791473)

------
andreigaspar
I like this, it illustrates the concept nicely

------
freefal
The <marquee> tag is a throwback! Love it (in a toy project like this)

------
leetrout
Code like this is interesting to me whenever I see it:

const g = [['', '', ''], ['', '', ''], ['', '', '']] // grid

Why not just be descriptive and call it `const grid`? It's like we're all
playing some game to be as terse and obscure as possible with variable names
in all languages.

~~~
leontrolski
Author here - I did put a warning in guys :-)

    
    
      Lots of the code looks pretty code-golfy - I promise I don't do stuff like this at work, neither should you
    

Happy to s/g/grid/ \- writing the whole exercise felt a bit golfy TBH.

~~~
galaxyLogic
>> if you change the state, it runs the function again, this returns a new
virtual DOM

What do you mean by "change the state"? How do you do that? By calling some
function, or perhaps by assigning a value to some property of a state-object?
OR by calling the function again with a different state-object?

Thanks

------
andrewstuart
Really not React in 33 lines at all.

~~~
pgt
[https://github.com/leontrolski/leontrolski.github.io/blob/ma...](https://github.com/leontrolski/leontrolski.github.io/blob/master/33-line-
react.js) captures the spirit of React.

The "diff & patch" pattern is super common, e.g. Git and Incremental by Jane
Street:
[https://github.com/janestreet/incremental](https://github.com/janestreet/incremental)

~~~
emmelaich
And curses!

> _The programmer sets up the desired appearance of each window, then tells
> the curses package to update the screen. The library determines a minimal
> set of changes that are needed to update the display and then executes these
> using the terminal 's specific capabilities and control sequences._

[https://en.wikipedia.org/wiki/Curses_(programming_library)](https://en.wikipedia.org/wiki/Curses_\(programming_library\))

------
tudorizer
This also doesn't mix pseudo-HTML (JSX?) with JS code, so that's cool.

As someone who has sort of lost contact with the front-end world, it's nice to
see a coding TL;DR.

~~~
frosted-flakes
Why is it good? JSX is just a declarative syntax for creating DOM elements
(usually), and in practice it works really well.

------
fnord77
OT this is the first time I've seen the arrow function syntax in ES6. My god
it is horrible.

    
    
        const Cell = (value, i, j)=>m('button.cell',
            {onclick: ()=>move(value, i, j)}, value
        )
    
    

I can see why people are using things like clojurescript instead.

~~~
TheRealPomax
We all hate new syntax that we feel isn't "samey" enough to what we already
know. That doesn't mean it's gross, it just means you learned something about
your own preconceptions of what you thought JS was supposed to look and feel
like.

Thankfully, most of the time we learn that's just us, and move past that.
Arrow functions have been around for nearly five years now: they're fine. You
learn that they're just part of JS, and you see them everywhere in modern
code, just with normal indentation and sensible white spacing to keep them
perfectly readable (because minification will solve the "the code as written
is too verbose" part of the dev cycle).

The important thing is what they _do_ : bind declaration context as execution
context, so `this` keeps meaning the same thing, which drastically simplifies
a lot code.

In this case, there is simply no reason to use arrow functions at all, because
we don't need to preserve the declaration context (there is no `this` that
needs to be preserved), so if this were real code you'd probably use a normal
function, and your comment should probably be "why not this?"

    
    
       function makeCell(value, i, j) {
         function onclick() {
           playMove(value, i, j);
         }
    
         return build('button.cell', { onclick }, value);
       }
    

Or even, "actually, why not this?"

    
    
       /**
        * ...
        */
       function clickCell(evt) {
         const t = evt.target,
               d = t.dataset;
    
         playMove(t.textContent, d.i, d.j);
       }
    
       /**
        * ...
        */
       function makeCell(value, i, j) {
         const e = build('button.cell', value);
    
         e.setData({ i, j });
         e.click(clickCell);
    
         return e;
       }
    

To which the answer of course is "this isn't production code, arrow functions
are fine. I mean come on, it's just a fun little JS exercise, stop trying to
turn fun into work".

~~~
fnord77
I don't use javascript regularly so I have no notice of "samey". But I've been
exposed to enough of a variety of programming languages from APL to C to Rust
to Scala that I have a pretty good idea when some construct looks awkward or
unergonomic. ES6 looks like a nightmare. Even compared to the nightmare that
is Scala. And remember, Javascript's roots are scheme.

Here's the same thing in clojurescript. Tighter, balanced and no chaining of
symbols.

    
    
        (defn Cell [value i j]
          (m 'button.cell' {:onclick #(move value, i, j)}))

~~~
TheRealPomax
I've never used clojurescript (but have used perl, php, java, prolog, oocaml,
js, python, ...), so to me that: looks like a clusterfuck.

But of course, if I had a need where clojurescript was the best fit, and I had
to use it daily for an appreciable amount of time, I'd acclimatize to it and
consider this perfectly fine syntax. The same goes for ES6: it's objectively
just syntax. If you think it's a nightmare, that's because you're the one
doing the judging. Not because it is.

