Easily generate dynamic html using TSX

 
 
  • Gérald Barré

Creating dynamic HTML in JavaScript is tedious. You must call document.createElement("div"), set attributes one by one, and manually append children. The result looks like this:

JavaScript
function sample(className) {
    let div = document.createElement("div");
    div.classList.add(className);

    let a = document.createElement("a");
    a.href = "https://www.meziantou.net"
    a.textContent = "Meziantou";

    div.appendChild(a);
    return div;
}

This is verbose and hard to read at a glance. React addresses this with JSX, a JavaScript extension that lets you mix HTML and JavaScript. The same code written with JSX looks like:

JavaScript
function sample(className) {
    return <div class={className}>
                <a href="https://www.meziantou.net">Meziantou</a>
            </div>;
}

TypeScript also supports JSX through TSX, with full type checking. All you need to do is rename the file extension from .ts to .tsx. The compiler will then generate the following JavaScript:

JavaScript
function sample(className) {
    return React.createElement("div", { "class": className },
        React.createElement("a", { href: "https://www.meziantou.net" }, "Meziantou"));
}

The JSX is compiled to React.createElement(...) calls. Since TypeScript 2.1, you can configure the compiler to use your own factory function instead, as long as the signature matches. This means you no longer need React. A basic implementation of createElement looks like:

JavaScript
namespace MyJsxFactory {
    interface AttributeCollection {
        [name: string]: string;
    }

    export function createElement(tagName: string, attributes: AttributeCollection | null, ...children: any[]): Element {
        let element = document.createElement(tagName);

        if (attributes) {
            for (let key of Object.keys(attributes)) {
                element.setAttribute(key, attributes[key]);
            }
        }

        for (let child of children) {
            appendChild(element, child);
        }

        return element;
    }

    function appendChild(parent: Node, child: any) {
        if (typeof child === "string") {
            parent.appendChild(document.createTextNode(child));
        } else if (child instanceof Node) {
            parent.appendChild(child);
        } else {
            throw "Unsupported child";
        }
    }
}

Next, update tsconfig.json to point to your custom factory:

JSON
{
    "compilerOptions": {
        "jsx": "react",
        "jsxFactory": "MyJsxFactory.createElement"
    }
}

The same TSX file now compiles to:

JavaScript
function sample(className) {
    return MyJsxFactory.createElement("div", { "class": className },
        MyJsxFactory.createElement("a", { href: "https://www.meziantou.net" }, "Meziantou"));
}

You can now use JSX in TypeScript without any external dependencies. The only trade-off is that the return type of a JSX expression is any rather than the more specific HTMLDivElement. This is a minor limitation worth accepting for the improved readability.

If you create a lot of HTML dynamically, I'm sure you'll love this syntax 😃

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?