Skip to content(if available)orjump to list(if available)

Show HN: TinyJs React like framework in 35 lines of code

Show HN: TinyJs React like framework in 35 lines of code

45 comments

·January 26, 2025

Hi HN, I got to work yesterday and today and came up with a very simple way to create and manage state in TinyJS.

I end up creating a simple App to illustrate how to use the new createState function and custom components in a React like manner. Here's the code for it - https://github.com/victorqribeiro/tinyapp

Here's the PR for the createState function - https://github.com/victorqribeiro/TinyJS/pull/9

chrismorgan

A comment on code organisation.

https://github.com/victorqribeiro/TinyApp/blob/db92225cfd0ae...:

  export default function Input(state) {

      const inp = input({  // ①
          type: 'number', value: state.count, onchange
      })

      function update() {  // ②
          inp.value = state.count
      }

      function onchange() {  // ③
          state.count = parseInt(this.value)
      }

      state.addUpdate('count', update)  // ④

      return inp
  }
(Labelling comments mine.)

This ordering isn’t great: ① refers to ③, and ④ refers to ②. It’d be better to reorder them, at least to swap ② and ③, and remove the blank line separating ② and ④:

  export default function Input(state) {
      const inp = input({  // ①
          type: 'number', value: state.count, onchange
      })

      function onchange() {  // ③
          state.count = parseInt(this.value)
      }

      function update() {  // ②
          inp.value = state.count
      }
      state.addUpdate('count', update)  // ④

      return inp
  }
But you really can do much better:

  export default function Input(state) {
      const inp = input({
          type: 'number',
          value: state.count,
          onchange() {
              state.count = parseInt(this.value)
          }
      })

      state.addUpdate('count', function() {
          inp.value = state.count
      })

      return inp
  }
At least at this scale, that’s much easier to follow.

Inlining can improve other places too (though at larger scales it becomes more debatable):

  export default function Button(state) {
      return button({
          onclick() {
              state.count += 1
          },
          style: {
              color: 'black',
              border: 'solid 1px black'
          },
      }, '+ 1')
  }

gknoy

This is somewhat off-topic, but I am blown away by your use of uncommon characters like "①". They stand out so much that they (for me at least) make referencing parts of the code snippet so much easier to follow.

I see these _nearly never_, so rarely that I forgot they were available to use. I didn't realize HN supports them, or that I could probably use them in review comments on Github. Thank you for the inspiration! (now to figure out how to actually type them...)

chrismorgan

I typed them with my Compose key, which I bind to the key to the right of Space, typically RAlt: Compose ( 1 ) ⇒ ① (this is part of the default mappings in /usr/share/X11/locale/en_US.UTF-8/Compose).

I recommend investing in a good Compose key setup. When I was on Windows, I used WinCompose. On Linux, you just need to bind the key and it’ll work. macOS, no idea.

HN does block emoji, for better or for worse, but almost everything else is allowed. The thing that frustrates me most about HN’s Unicode restrictions are its normalisation of diverse spaces to U+0020 SPACE; if I typed THIN SPACE or NARROW NO-BREAK SPACE or NO BREAK SPACE or whatever (and I do) that’s what I wanted; and mangling U+200B ZERO WIDTH SPACE and U+200C ZERO WIDTH NON-JOINER is just bad, and has prevented me from accurately representing Telugu text before—Indic scripts use the latter.

lrpe

I'm more annoyed by them. They're so small on my work monitor that it's nearly impossible to tell them apart. They just look like smudges.

I don't get the need to use novel symbols like this when standard numerals would suffice.

chrismorgan

I’d be interested to see a screenshot. Something’s quite wrong if they look like smudges. I’d be interested to see what font’s being used, too, which you can find in Firefox’s dev tools, Inspector tab, Fonts pane, Fonts Used section.

atum47

> (though at larger scales it becomes more debatable)

My thought exactly. In a small component as the Button I agree with inline everything (almost did it myself cause I knew people would read this code) but I have decided to keep it as is for readability. In a more complex component as the Canvas I think you'd agree with me that the long winded version is easier to grasp what's going on.

chrismorgan

In Canvas, I’d still prefer to inline everything, but I recognise that you’re getting to the scale where it’s more debatable—that it’s not just unquestionably superior, as it is in the smaller cases like this. (You “decided to keep it as is for readability”? I’m confused; I find the way you wrote it vastly less readable, it’s not even close.)

When you don’t inline the methods, really all that you get out of it is a list of properties, without the method bodies getting in the way. The question then is, is that useful? If you’re in a simple text editor, then sometimes definitely yes. But it’s duplication, it’s more scope for mistakes, and all up there’s a reason why things like C headers have been falling out of favour as an authoring style. More advanced editors these days can present file outlines, which will work pretty well with the function style, and would work even better with class methods, but may not work so well with the inlined object-shorthand methods (I don’t know, I’m not in the habit of using such outliners—it’s possible for them to take these methods, but it wouldn’t surprise me to find them omitting them altogether).

It’s a similar deal to whether you write `export function a… export function b…` or `function a… function b… export {a, b}`. Some prefer one, some the other. Sometimes there are compelling reasons to use one or the other, independent of preference. I get the feeling immediate export is more popular.

null

[deleted]

bolfe11

I created a typescript version of your framework

https://github.com/guilhermebolfe11/TinyTS

atum47

Nice

null

[deleted]

campak

This is cool. Keep up the good work and enjoy tinkering!

codingdave

So, if you are building this off 'window', you are basically just adding syntax sugar to one big global variable. That is absolutely a simple solution to storing state. In one place. But that is not comparable to the functionality of React, so I'm not sure I agree that it is a "react-like framework".

atum47

I'm guilty of using React as framework to try to copy from. After working with it for the past 5 years I end up wanting something like it but with less bloat to use in my personal projects. So, what I tried to do: have functional components with JSX like syntax and a managed state that is in sync with the UI. Giving those 3 features and the syntax, I used the term React like.

MarcelOlsz

Check out "preact" if you haven't already [0]

[0] - https://preactjs.com/

whizzter

While I see a fair bit of elegance in your experiment, IMHO I also think that for it to be React-like and start mentioning code size one should at least handle structural changes (ie adding/removing items like in a todo-app).

I made a minimal Vue-inspired templating thingy a while back and a fair bit of the codesize (part due to impl and part due to architecture to make it solid) is spent to handle cases of structural changes (adding/removing items, updating event listeners,etc).

atum47

> (ie adding/removing items like in a todo-app).

I have made such an app using TinyJS, would you like to see it?

here: https://github.com/victorqribeiro/TodoApp

EGreg

I never understood the fascination with JSX. Stuffing HTML into JS? You can stuff JS into HTML natively LOL.

Why not use HTML templates and Web Components, or something? I would have thought React would become like jQuery, after Web Components were implemented natively in browsers, just like jQuery's functionality was. But it's still very popular.

MarcelOlsz

>Why not use HTML templates and Web Components, or something? I would have thought React would become like jQuery, after Web Components were implemented natively in browsers, just like jQuery's functionality was. But it's still very popular.

Us frontend devs are so lost in the framework sauce that there is a 5 year lag between what web api's can do and when it hits our slack channels.

WorldMaker

> Why not use HTML templates and Web Components, or something?

At least in my experience, the best way to write both HTML Templates and Web Components is with JSX. The type checking and language services of JSX are still something of a best in class for serious type safety in complicated data binding and reactivity.

HTML Templates have "slots", but those are the simplest of holes and on the one hand support any HTML you want to place inside those holes, but on the other hand aren't a very deep template language for interactivity.

Web Components have some auto-magic with "slots" if you plan to use your HTML Templates to populate the Shadow DOM. But the Shadow DOM is super complicated, hard to style, and entirely optional. It's also still not a deep enough auto-magic to do deep interactivity easily.

We also still don't have "HTML Modules" even in strong polyfill, so distributing HTML Templates for Web Components is still something of an open problem.

Most highly interactive Web Components still have a template engine inside, even when they work alongside/with HTML Templates. JSX is still a great template language.

React itself may still be the jQuery of JSX-based because we seem to be swinging from Virtual DOM approaches back to "Real" DOM approaches, but JSX can be used for more than just Virtual DOM. (I've been successfully using my own library Butterfloat that uses JSX but is not a Virtual DOM in nice to tree-shake, wonderfully type safe Web Components.)

mightyham

I don't think it's that hard to understand the appeal of JSX. If you want your interface to be modeled functionally (state as input, view as output), it makes way more sense for your view to be the result of a function in contrast with having app logic "stuffed" into a mutable view template.

I'm also not sure why you are offering Web Components as an alternative because it's exactly what you are criticizing, stuffing HTML into JS. It's basically just a more limited and less ergonomic version of React class components, that comes with it's own host of unique headaches.

jeswin

> I never understood the fascination with JSX.

HTML templating (without JSX) gives you markup as strings, which means you don't get type checks, auto-complete, syntax highlighting etc.

globalise83

Web components are still a little tricky to work with. For example, passing down a complex state to child components is easy in React, but in native web components not so straightforward. Stencil.js is a nice choice for that, but it is just as complicated as React to learn.

thomasfromcdnjs

expressiveness

ajkjk

they're just so much easier to think in

jy14898

Maybe I'm missing something, but there isn't any global state attached to window? They attach a constructor for state to the window, but that isn't the state itself - it just returns a smart state object.

atum47

    const appState = {...state}
With that line I end up creating a appState inside the createState function which uses the state dictionary just as a template.

Would it be better if I attached the things I need to the state object that I receive?

    function createState(state) {
        state._update = () => ...
        ....
        return new Proxy(state, ...)
    }

legit question.

null

[deleted]