Skip to content

Instantly share code, notes, and snippets.

@thomaswilburn
Created May 9, 2022 15:23
Show Gist options
  • Save thomaswilburn/a001e1c09ffb2c33d394f5f549bf29a6 to your computer and use it in GitHub Desktop.
Save thomaswilburn/a001e1c09ffb2c33d394f5f549bf29a6 to your computer and use it in GitHub Desktop.
React but make it imperative

What if React was imperative instead of being functional?

When you write JSX, it compiles down to nested calls to createElement() (or the equivalent, I don't know if that's still the function React uses). Because this ends up being a single recursive function call, you can only use value expressions inside the code--that's why you have to map() when you loop, and you have to use ternaries instead of actual conditional statements.

Over time, the React team's thinking about architecture has clearly shifted a lot, to the point now where hook functions create implicit, hidden state that's preserved in the order of the function calls. What if JSX worked this way? What if when you wrote a template, instead of a single function call with nested expressions, it used imperative, IMGUI-like function calls instead? In that case, the raw code might look something like this:

template(html => {
  var { footer, a, button, text, div, ul, li } = html;
  div`.outer`;
  div`.container`;
    button`.toggle`({ "aria-pressed": false });
      text("hello");
  div.close();
  ul`.second`;
  for (var i = 0; i < 3; i++) {
    li();
      a`.page`({ href: "#" + i });
      // console.log(link);
        text("Page " + (i + 1));
    li.close();
  }
  div.close();
  footer();
    text("footer section goes here");
});

Which creates:

<div class="outer">
  <div class="container">
    <button class="toggle" aria-pressed="false">hello</button>
  </div>
  <ul class="second">
    <li><a class="page" href="#0">Page 1</a></li>
    <li><a class="page" href="#1">Page 2</a></li>
    <li><a class="page" href="#2">Page 3</a></li>
  </ul>
</div>
<footer>footer section goes here</footer>

The advantage is that you now actually have all the power of JS keywords and syntax, including regular loops, conditional statements, and potentially async/await. The bad news is that it's an abomination before god and man.

Code for generating this follows:

function template(callback) {
  var template = document.createElement("template");
  var branch = template.content;

  var tagFunction = function(tagName) {
    var update = function(input) {
      if (!input) {
        element(true);
      } else if (input instanceof Array) {
        var classes = input.join("").split(".").filter(c => c);
        element(true).classList.add(...classes);
      } else {
        for (var k in input) {
          element().setAttribute(k, input[k]);
        }
      }
      return update;
    };

    var element = function(force) {
      if (force || !update.element) {
        console.log(`Creating ${tagName}...`);
        update.element = document.createElement(tagName);
      }
      if (branch != update.element) {
        console.log(`Appending...`);
        branch.append(update.element);
        branch = update.element;
      }
      return update.element;
    };

    update.close = function() {
      if (update.element && update.element.parentElement) {
        branch = update.element.parentElement;
      }
    }

    return update;
  };

  var proxy = new Proxy({}, {
    get(_, tagName) {
      switch (tagName) {
        case "text":
          return str => branch.append(str);

        default:
          var tag = tagFunction(tagName);
          tag.close = function() {
            var closest = branch.closest(tagName);
            var parent = closest && closest.parentElement ? closest.parentElement : template.content;
            branch = parent;
          };
          return tag;
      }
    }
  });

  callback(proxy);

  var clone = () => template.content.cloneNode(true).children;

  return { template, clone };
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment