Easily generate dynamic html using TSX

 
 
  • Gérald Barré

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

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 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 mixing JavaScript and HTML. Using JSX, the previous code can be written more conveniently:

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

TypeScript also supports 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:

JavaScript
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 method as long as the signature remain the same. Thus, you do not rely on React. A basic implementation of the createElement function is:

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

Now we need to change the tsconfig.json file:

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

The previous file is now converted to JavaScript as:

JavaScript
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!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub