Easily generate dynamic html using TSX

  • TypeScript
  • Web

Creating dynamic html code in JavaScript is not fun. You have to use document.createElement("div"), then set it's attributes one by one, then add children. Finally, it looks like:

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 very verbose, and it's hard to have a quick look at the final html code. React tries to simplify with an extension to JavaScript: JSX. JSX allows to mix JavaScript and html. Using JSX, the previous code can be written in a more convenient way:

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

TypeScript also support JSX syntax but fully-typed with TSX. All you need to do is changing the extensions of the previous file from .ts to .tsx. The compiler will generate the following JavaScript file:

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

The html part of the document is converted to React.createElement(...). Since TypeScript 2.1, you can configure the compiler to use your own method as long as the signature remain the same. Thus, you do not rely on React. A basic implementation of the createElement function is:

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";
        }
    }
}

Now we need to change the tsconfig.json file:

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

The previous file is now converted to JavaScript as:

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

You can now use the JSX syntax in TypeScript without any dependencies. The only shortcoming is that the return type of the first function is HTMLDivElement (well-typed), whereas the return type of tsx function is any. I don't know if this will change in the future but this is a small trade-off to simplify your TypeScript code.

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 on Twitter or by email!

Follow me:
Enjoy this blog?Buy Me A CoffeeDonate with PayPal