Show HN: 33 line React

(leontrolski.github.io)

418 points | by leontrolski 1483 days ago

28 comments

  • pomber 1483 days ago
    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/

    • aenario 1483 days ago
      Love the format with "live-coding". Is it git-based? Did you open source it?
      • pomber 1483 days ago
        • capableweb 1483 days ago
          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 1483 days ago
            Hey, that's what Flems is for. Check out 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
            • capableweb 1483 days ago
              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 1483 days ago
                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 1482 days ago
                  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.

    • enthusiast94 1477 days ago
      Thanks for that super useful article @pomber! I read it a while ago and attempted to convert it to TypeScript and do a few extensions, here's my attempt: https://github.com/manasb-uoe/didact.
    • lhuser123 1483 days ago
      This is really good.
    • geniium 1483 days ago
      Nice post!
  • anonytrary 1483 days ago
    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 1483 days ago
      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

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

      • pier25 1483 days ago
        I used Mithril recently in a small project and it's quite awesome.

        I didn't miss any of React's features plus it includes a router and an http client in less than 10kB gzipped. Heck, my whole application weighted something like 38kB. Not the JS bundle, everything.

        The other greatest thing about Mithril is that there is no reactive state per se. Your state are just vanilla objects and vars and then you tell Mithril to redraw the whole thing. It sounds wasteful but it's quite fast and the developer experience becomes a joy.

        Mithril is like a sharp sushi chef knife. Since it leaves all the application architecture to you you better know what you're doing otherwise you will cut yourself pretty badly.

      • sdegutis 1483 days ago
        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

        • modarts 1483 days ago
          • chrisweekly 1483 days ago
            +1 for cyclejs! Strange to me that it hasn't caught on more
            • sdegutis 1483 days ago
              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.
            • pier25 1483 days ago
      • anonytrary 1483 days ago
        It depends on the use case. For applications that experience a high frequency updates near the top of the tree, a naive solution would typically block user input.

        Mithril's solution is very simple but using a setInterval(render, 16.6667) has its drawbacks that can lead to jankiness in the UX. React 16 from what I understand is almost a complete rewrite of React and does some smart stuff with animation frames to ensure an optimal UX.

        • lhorie 1482 days ago
          Mithril does use requestAnimationFrame unless you're in some version of IE. But Mithril rendering was never intended to run as the inner loop of an animation. Lifecycle hooks and CSS animations are preferable instead.

          React Fiber basically breaks down large animation payloads so each "chunk" can fit within one frame budget, but at the cost of a lot of untreeshakeable internal and peripheral complexity in React core.

          • anonytrary 1482 days ago
            Agreed; all of complex scheduling solutions using RIC/RAF and priority queueing are probably not needed for 95% of webapps. The primary need is good state management and a good side-effect framework (e.g. hooks, lifecycle methods).

            Do you intend for Mithril to support this sort of "incremental" priority work handling?

    • masklinn 1483 days ago
      Snabbdom[0] might be a better one there, it's not quite 33 lines (the core is about 200), but it is intended to be usable and used in production systems, either on its own or underlying a components system or whatnot.

      [0] https://github.com/snabbdom/snabbdom

    • blablabla123 1483 days ago
      I wonder how well the event binding works. For me this is the main reason to use React because it avoids all the mess that can come from that. Apart from that, React performance is much better than any naive re-rendering solution, on the other hand it's worse than programmatic diffing with native APIs or jQuery...
      • leontrolski 1483 days ago
        In what way would you say the event binding here differs from React's? (Genuinely interested).

        The main difference from where I'm sitting is that React has a concept of "Component state" - updates to which trigger local redraws. Here you have to manually call:

          renderNoughts()
        
        any time you change state, this rerenders the whole mount point.

        If I'm honest, React's state stuff has always seemed a little strange to me Would love people to try explain the point to me, is it just a performance related enhancement?

        • blablabla123 1483 days ago
          I've never deep-dived into that but it's possible to declare components as pure. In that case, as least in theory, React is able to only perform the absolute minimum amount of operations. [1] On the other hand, about binding, the worst-case scenario is that after an update onclick handlers stop working or memory leaks start emerging. It's easy to run into these problems when working for instance with older Frameworks like Backbone.js, which register click handlers procedurally but don't manage the bindings - automatically - during the whole life cycle. Also I've observed such problems with one of the minimalistic React clones, I think it was Preact or Inferno.

          [1] https://medium.com/@esamatti/react-js-pure-render-performanc...

        • _bxg1 1483 days ago
          React's state stuff is just a really basic, batteries-included solution that works for basic things but doesn't really scale. There's nothing special about it in terms of performance. Once you get beyond a certain level of complexity in your app, and need to bring in richer state management, React's mechanisms become dead weight.

          The one exception being contexts. Contexts allow you to hook into react's virtual DOM and "talk about" a subtree of it instead of just the very next layer down. This is something that an external library can't replicate, so it's more of a fundamental primitive than setState and hooks are.

  • leontrolski 1483 days ago
    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.
  • red_admiral 1483 days ago
    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 1483 days ago
    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 1483 days ago
      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...

      • dahart 1483 days ago
        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.
    • petilon 1483 days ago
      Consider adding something like this for jsx (200 lines): https://github.com/wisercoder/uibuilder

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

    • dang 1483 days ago
      (This was originally a reply to https://news.ycombinator.com/item?id=22777156)
    • ngrilly 1483 days ago
      Agreed, except for routing which is not a part of React per se.
    • urvader 1482 days ago
      Look at Pureact (I’m the author) and make a pull request with your diff code to make the whole thing without any dependencies? Would be supercool!
  • jonahx 1483 days ago
    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/

    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.

    • hencq 1483 days ago
      I'm not sure if you're the author, but in case you are I just want to say well done on the tutorial! I really like the way it's built up and not only shows the pattern, but also the why behind the pattern. I will definitely give this a try with Mithril.
    • benbristow 1483 days ago
      With none of the community support or third-party components that the React ecosystem has?
      • keb_ 1483 days ago
        Why use any new & upcoming tool when an older, more popular tool inevitably has more community support and third-party components?
  • codegladiator 1483 days ago
    Looks like mithril (the m object and m.render almost the same) ?

    https://mithril.js.org/

    • leontrolski 1483 days ago
      Yeah, it's very mithril influenced (I love that library) - I namecheck them in the article at the top.
      • codegladiator 1483 days ago
        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 1483 days ago
    > 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 1483 days ago
      It seems perfectly well named to me! I love when code bases have more lines of comments than actual code.
      • ricardobeat 1483 days ago
        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 1481 days ago
          It could also be because the code is terse. In that case it makes sense to explain what's going on.
  • seanwilson 1483 days ago
    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 1483 days ago
      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
      • websitescenes 1483 days ago
        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 1483 days ago
          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 =)
        • seanwilson 1483 days ago
          I don't know how React does it so it was a genuine question. I'm assuming they do some smart diffing to be more efficient.

          It'll be irrelevant for most apps but I'm curious how much less efficient all this is compared to (in CPU time, not developer time) updating the DOM directly e.g with:

                document.querySelector('.timer').textContent = "5 seconds"
    • leontrolski 1483 days ago
      Mithril's approach (as robertakarobin refers to) is that diffing VDOMs is cheap (where as updating the DOM itself is expensive), the "library" in this post takes the same approach.

      It's interesting, there's a lot of chat about performance with these frameworks, yet I'm not sure how much is relevant to the real world. When I do eg a

        document.querySelectorAll('*')
      
      on airbnb map view (I guess a pretty good example of a mid complexity SPA), there are 3407 DOM elements - doing a diff on the VDOM elements of these should quick.

      If I were doing something like a clock on a page (or an animation for example), I'd probably just do it out-of-band of my framework. These kind of things tend to be few and far between anyways.

    • learc83 1483 days ago
      That's why I prefer the Svelte way of doing things. It compiles down to code that directly changes the DOM, but only the parts that need to be updated.
  • zserge 1483 days ago
    Nice project! I've also recently made a React clone in <1KB, with JSX syntax and Hooks - https://github.com/zserge/o
  • jmchuster 1483 days ago
    If you came to this comments section looking for an actual lightweight standalone version of react, preact+htm works wonders.
    • lioeters 1483 days ago
      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__ 1483 days ago
    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 1483 days ago
      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__ 1483 days ago
        You think so?

        What would be different if

            Document.createElement('a')
        
        Would look like

            <a/>
        
        ?

        The rest would still be imperative code.

        • ken 1483 days ago
          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.

        • colejohnson66 1483 days ago
          It’s been a bit since I’ve used JS (so I may have the latter syntax wrong), but I’d prefer the JSX style of

              var elem = <a href="#test">Test</a>;
          
          over

              var elem = document.createElement("a");
              elem.href = "#test";
              elem.innerText = "Test";
          
          jQuery allows one to do something similar to JSX (which is nice):

              var elem = $("<a href='#test'>Test</a>");
        • capableweb 1483 days ago
          The first one is imperative and locks the implementation to use the Document object and the createElement function.

          The second one is declarative and leaves it up to the receiver to handle however they seem fit.

          I'm not arguing one is better or worse, or that we would be better off if we we're using the JSX way with native support in the browsers, just explaining how I see the differences between the two.

  • sfvisser 1483 days ago
    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 1483 days ago
      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 1483 days ago
    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?

    • masklinn 1483 days ago
      Because the DOM is a very specific style of API which is both extremely procedural and extremely heavy, your average DOM element is full of cross-links to other elements, so generating a DOM is both a pain in the ass and quite expensive. It's also extremely stateful, so while you could generate a new DOM and replace the old one you'd lose all information in flight e.g. focus.

      Browsers might be able to provide a lighter vdom layer but… well first I'm not convinced the gain would be that big, and second what would the API be? It's not like there's a clear winner that has emerged, react is quite popular but hardly the only contender, and react has kept evolving its API so the sort of stability you'd want enshrined in a browser isn't exactly there.

    • jfkebwjsbx 1483 days ago
      Because browsers were initially meant for another job: displaying static, declarative documents.

      Slowly, they gained more and more features and performance until people started creating single-page apps, where you need to re-render on state changes all the time.

      So people invented the best ways they could do so within the boundaries of the browser: the diff/patch approach, in order to work around the performance issues of the document model.

      And in some time, perhaps browsers end up standardizing a way to do this natively. Or perhaps another app-model rather than doc-model (doubtful). They already tried to start with templates and whatnot, somewhat unsuccessfully.

      In summary: the document model (and browsers) were not designed for that. The Web is a mess of features and features on top of features that has ended up with roughly a secondary operating system on top of a real operating system. The only (but critical) advantage of this model is that it is a de facto standard.

    • Silhouette 1483 days ago
      The big advantage of a library like React is that you can specify your UI declaratively once, instead of having to specify imperatively how to handle every possible state transition. This can dramatically reduce the number of different rendering cases you have to write if you have a lot of state to manage.

      All the virtual DOM stuff is just an implementation detail that makes the declarative stuff fast enough to use in production. A more naive implementation that rebuilt your entire (real) DOM on every state change would not be fast enough for anything but the most simple applications, because DOM updates in browsers are currently a relatively slow operation.

      Browsers could provide an API to more efficiently update large sections of the DOM at once, using a React-style diff algorithm or otherwise, but currently that isn't a feature they offer. If they did, you'd be correct that there would be less need for libraries like React.

    • pier25 1483 days ago
      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 1483 days ago
        If my cat was a cow, she'd give me milk. The hard part is turning my cat into a cow.
        • Silhouette 1483 days ago
          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 1473 days ago
            But most people are -still- using CSS preprocessors today...
    • quezzle 1483 days ago
      It’s a framework.

      Similar questions are:

      Why not build rails into ruby?

      Why not build Django into python?

      Why not build qt into c++?

      It’s a programming flow and model, dom diffing is just one part of it.

      • ken 1483 days ago
        Those examples are all programming languages.

        "Web browser" is not a programming language. It's practically a complete operating system, and they have not in the past shown much reluctance to add features to that big ball of mud, when it helps programmers.

        • galaxyLogic 1483 days ago
          Right. I think the value of this "React in 33 lines" article is it shows us such an API would be easy to implement and add on by the browser-makers.

          Would it be as fast as React? Well it wouldn't need to be, but it well could easily be since it would be implemented in native code.

          It could be an extension to the Shadow-DOM API, right?

    • redis_mlc 1483 days ago
      Vanilla-js. Werd.
  • peey 1477 days ago
    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 1483 days ago
    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?

    • masklinn 1483 days ago
      > Why is classList read-only, I can think of any valid reason.

      Because it's a live DOMTokenList, not a computed property. That is if you keep a reference to a classList and mutate the className parameter, classList will reflect the changes.

      classList could also be a computed property which, when assigned to, clears the underlying token list and adds all elements but I'm guessing the developers of the API saw this as unnecessary complexity given you can do the same thing by setting className.

      The point of classList is the ability to more easily check for and toggle individual classes, if you don't need that capability you just don't use classList.

      > Perhaps ele.className=(v.classes).join(" ") is a valid solution?

      Yes.

      • leontrolski 1483 days ago
        Great answer, thanks! Embarrasingly, I didn't even know about className :-)
        • tonyarkles 1483 days ago
          I’m not sure this is the right place, but... I see a potential subtle bug there too: it doesn’t seem at first glance that the classes get added in the same order as they’re specified in the virtual element? If I made div.bar and then changed it to div.foo.bar, properties of .foo could override properties of .bar because it looks like it would end up with classList [“bar”, “foo”] instead of [“foo”, “bar”]? Maybe?

          Edit: please don’t take this as criticism. I love the project and think it’s awesome! That’s why I took the time to read through the source and grok it!

          • masklinn 1483 days ago
            > If I made div.bar and then changed it to div.foo.bar, properties of .foo could override properties of .bar because it looks like it would end up with classList [“bar”, “foo”] instead of [“foo”, “bar”]? Maybe?

            What properties are you talking about here? Despite the name, classes are really an unordered set, the order of classes on the element should not matter: when CSS gets applied, properties are prioritised based on the most specific rule, then the latest rule. That is the prioritisation of CSS properties should depend entirely on the CSS and not in any way on the order of the class attribute / className property.

            • tonyarkles 1483 days ago
              So let’s say .foo sets “padding: 8px;” and .bar sets “padding: 4px;”. It’s been a while, but I’m pretty sure that <div class=“foo bar”> ends up with 4px padding, and <div class=“bar foo”> ends up with 8px padding. I could be wrong though and will test it out when I’m back in front of a real computer.

              Edit: Woah... It looks like I am incorrect. Nifty! I'm amazed I have never been burned by that before!

              Edit 2: I suspect the reason I've never been burned by that before is because overrides like that have generally been applied over top of some kind of generic CSS file and the generic one was loaded first; since whichever rule is declared later is considered to have precedence, the override file wins over the generic file. My mind is kind of blown right now that over 20 years of web development I have never encountered this problem!

    • leontrolski 1483 days ago
      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".
  • uk_programmer 1483 days ago
    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

  • diablo1 1483 days ago
    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

  • jxub 1483 days ago
    Weirdly triggered by the function arrows lacking spaces around them. Good work though
  • CitrusFruits 1483 days ago
    Anyone who likes the idea of a minimal virtual DOM (like this) will probably appreciate MaquetteJS: https://maquettejs.org/
  • leontrolski 1481 days ago
  • andreigaspar 1483 days ago
    I like this, it illustrates the concept nicely
  • freefal 1483 days ago
    The <marquee> tag is a throwback! Love it (in a toy project like this)
  • leetrout 1483 days ago
    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 1483 days ago
      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 1483 days ago
        >> 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

    • jackewiehose 1483 days ago
      I have no problem with abbreviations over a limited scope but looking at this code[1] I can't figure out what the author meant by 'm' and that makes me wonder if the author does know what he's doing.

      [1]: https://github.com/leontrolski/leontrolski.github.io/blob/ma...

      edit: Ok, I should have read the article first. It's probably for https://mithril.js.org/

      • leontrolski 1483 days ago
        Using m or h as VDOM constructors are mithril/hyperscript-isms, I guess that's not obvious if you not worked with those or similar libraries, sorry!
        • jackewiehose 1483 days ago
          yep, I figured that out by reading the actual article instead of straight jumping to the source and my edit was too slow. I'm sorry ;-)
    • pier25 1483 days ago
      Totally agree.

      Code should be stupidly obvious so that reading it consumes as less cognitive resources as possible.

      • bonestamp2 1483 days ago
        Yep, let the minifiers and obfuscators handle the minification and obfuscation.
    • jonplackett 1483 days ago
      So many code examples have things like this and worse.

      One I saw the other day had about 10 variables created in one part and then these were all placed into another object that had the same names as all the variables.

      You also see a lot of examples with loads of other irrelevant code in them.

      Just keep it simple please!

    • scelerat 1483 days ago
    • grawprog 1483 days ago
      If that grid was going to be used in a single loop or something I may write it like that but any larger scope and it would definitely have a descriptive name.
    • JustSomeNobody 1483 days ago
      It wouldn't annoy me so much if they hadn't put the comment. I can clearly see it's a grid.
    • rpunkfu 1483 days ago
      cries in golang
      • leetrout 1483 days ago
        For small functions / blocks and descriptive types I’m ok with it in Go BUT I always use descriptive names anyway. So much easier to keep context in my head.
        • libria 1483 days ago
          I thought the whole single-char-var thing was for very short-lived variables.

          I very much prefer

              for c in internalProductCategories
                 p = fetchProductsInCategory(c.id)
                 m = getAccountManagerName(c.id)
                 results.append({categoryName: c.name, products: p, manager: m.name})
          
          to

              for internalProductCategory in internalProductCategories
                 products = fetchProductsInCategory(internalProductCategory.id)
                 manager = getAccountManagerName(internalProductCategory.id)
                 results.append({categoryName: internalProductCategory.name, products: products, manager: manager.name})
          
          They're both clear, neither needs comments, but one is unnecessarily verbose.
          • ysavir 1483 days ago
            While that's definitely better than single letter variables everywhere, I still think an explicit variable name is better.

            In general, I follow a rule of "If I can't search the file for this variable and find it easily, it's a bad name".

            • Silhouette 1483 days ago
              Isn't this the standard argument for making names more specific as the scope where they are visible gets larger?

              Something only used within a short function, so it only ever appears within a few lines of where it's defined, can probably be very short or even a single letter, and that conciseness can be beneficial.

              Something used elsewhere in a file should probably have a more descriptive name, so it can be easily distinguished and located.

              Something exported from a module and used elsewhere in the program should probably have both the more descriptive element and then, one way or another, some further way to identify that it's the version from that particular module that is being used.

              • ysavir 1483 days ago
                I get what the argument is saying, but there might still be times where I want to find every mention of a variable within a 3-line block. If that variable is a single letter, and possibly a letter that's used 10-20 times in the code in that block, I would consider that block confusing.

                Ultimately, I don't think there's any hard rule or standard, just an evaluation of how easy it is to understand the code. Sometimes a single-letter variable will be fine, sometimes not.

                But I'd bet a 4-letter variable will always lead to code that is easier to understand.

                • karmakaze 1483 days ago
                  What if the references are all [i] or [i + 1]?
                  • Red_Leaves_Flyy 1483 days ago
                    Put a comment block at eof defining them and their usage. Use these variables in such a way that ctrl,f replace all with a descriptive name doesn't break anything except the line count and pretty print.

                    If you want to get really clever, write a script that automatically renames everything and reflows the file on commit.

                    Damn it. Now I need to make this...

            • bjpbakker 1483 days ago
              > "If I can't search the file for this variable and find it easily, it's a bad name"

              Like parent mentions, this is about scoping. My rule about short variables is that I have to see their _full_ use (including declaration) in only a few lines on the screen.

              Personally I prefer single letter variables especially in lambdas for consistency (x, y, z).

          • Kaze404 1483 days ago
            The latter requires you to read the assignment to figure out what the variables mean.
            • ludamad 1483 days ago
              This is true, it is classic data denormalization - in the latter example, if all we want to gloss is the results appending, we need to read less
            • Silhouette 1483 days ago
              On the other hand, the former is much more scannable, so maybe that doesn't matter.
              • Kaze404 1483 days ago
                I don't understand what this means.
                • Silhouette 1482 days ago
                  Just that the shorter names make for a more concise presentation, which in turn is quicker to scan than having to see and parse all the long names to reach the same conclusion.
          • ppeetteerr 1483 days ago
            Strongly disagree there. If there was a crash at line 4, in the first example you would only see this:

            > results.append({categoryName: c.name, products: p, manager: m.name})

            In the second example you would see this:

            > results.append({categoryName: internalProductCategory.name, products: products, manager: manager.name})

            Do you know what the error would be? Probably that you're assigning the manager's name to a property called `manager` ;)

    • caymanjim 1483 days ago
      Comments in code are almost always an indication of poor naming choices. I don't get it either.
      • Supermancho 1483 days ago
        He saw grid in his head and thought g was fine since he was doing a minimal poc. It's not something that's hard to understand.
    • brudgers 1483 days ago
      Aesthetically, it's consistent with the article.

      Technically it's consistent with ordinary practice. e.g.

        import naughtsAndCrosses as nan
      • gtf21 1483 days ago
        I'm not sure this is "ordinary practice", especially as `nan` often refers to "not a number" as an error category.

        Typing full words doesn't take that much longer than acronyms, and results in more readable code (although this is well enough established at this point that I don't think I need to go over it).

        • brudgers 1483 days ago
          Bugs are ordinary practice. Importing naughts and crosses is not. I giggled mischeviously when I wrote it. Particularly since it looked Pythonic. YMMV.
  • andrewstuart 1483 days ago
    Really not React in 33 lines at all.
    • pgt 1483 days ago
      https://github.com/leontrolski/leontrolski.github.io/blob/ma... 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

      • emmelaich 1483 days ago
        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)

    • dang 1483 days ago
      The submitted title was "React in 33 lines". We've since changed it. This is a case where rewriting the title did the article a disservice. To my ear there is a subtle but significant difference between that and "33 line React", which is what the author actually said.

      "React in 33 lines" sounds reductionist. With the actual title, there's an implicit "a" (i.e. an indefinite article) at the start, i.e. "A 33-line React". In other words: a short exercise exploring the core idea of React. Not a claim of total equivalence.

      Submitters: please read the site guidelines. They include "Please use the original title, unless it is misleading or linkbait; don't editorialize." Much of the time, when people rewrite a title, they make it more misleading or linkbait, even if they didn't mean to.

      https://news.ycombinator.com/newsguidelines.html

    • Jean-Philipe 1483 days ago
      Care to elaborate?
      • anonytrary 1483 days ago
        It's a very, very minimal implementation. The diff algorithm they use does not really have the necessary optimizations implemented.
        • capableweb 1483 days ago
          Not all applications have a requirement around "necessary optimizations". Sometimes if the application is simple enough, something like this submission could be good enough.

          Or the constraints could be different where the bundle size vs performance implications could be worth trading.

          Depends on context :)

          • anonytrary 1483 days ago
            You seem to be talking about something else. We're talking about how this library is not React in 33 lines. It could be better labelled as a "VDOM framework in 33 lines".
  • tudorizer 1483 days ago
    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 1483 days ago
      Why is it good? JSX is just a declarative syntax for creating DOM elements (usually), and in practice it works really well.
    • s_y_n_t_a_x 1482 days ago
      JSX is syntactic sugar for nested function calls, it is JavaScript.

      That's why you can do anything (declarative) in them. You can loop, have conditionals, etc. without having to learn a template syntax.

  • fnord77 1483 days ago
    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 1483 days ago
      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 1482 days ago
        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 1479 days ago
          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.

    • pier25 1483 days ago
      It's not only a syntax for anonymous functions. It also binds the function to the context where it's defined.

      https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

      Looks weird at first but you get used it pretty quickly. And I gotta say the auto binding thing is awesome.

      • fulafel 1483 days ago
        Re the Clojure comparison, it's not much of a consolation as the function binding problem doesn't exist there.
    • hombre_fatal 1483 days ago

          add = (a, b) => a + b
      
          xs.filter(x => x > 10)
      
      Are so elementary I'm not sure how they can look new to you unless you're new to programming.
    • pgt 1483 days ago
      Wait till you see what it does to scope!