Easily generate dynamic html using TSX
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:
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:
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:
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:
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!