Skip to main content

JSX

Basics​

JSX is a special syntax for templates which allows you to write HTML-like code. Each component has a render function that returns a JSX template.

import { Component } from "sinho";

export class HelloWorld extends Component("hello-world") {
render() {
return (
<div class="hello-world">
<h1>Hello, World!</h1>
<p>This is a paragraph</p>
</div>
);
}
}

In this example, the HelloWorld component will render a div element with two children, an h1 and a p element.

JSX will be transpiled into normal JavaScript. The above code is equivalent to:

import { Component, h } from "sinho";

export class HelloWorld extends Component("hello-world") {
render() {
return (
h("div", { class: "hello-world" }, [
h("h1", {}, "Hello, World!"),
h("p", {}, "This is a paragraph"),
])
);
}
}

For native HTML tags, you can use a shorthand notation:

import { Component, h } from "sinho";

export class HelloWorld extends Component("hello-world") {
render() {
return (
h.div({ class: "hello-world" }, [
h.h1({}, "Hello, World!"),
h.p({}, "This is a paragraph"),
])
);
}
}

In addition to HTML tags, you can also use a custom element class as node, even those that are not written with Sinho:

<HelloWorld />;
<SimpleGreeting name="John" />;

// This is equivalent to:

h(HelloWorld);
h(SimpleGreeting, { name: "John" });
warning

Custom elements need to be defined first before they can be rendered. Custom element classes can only be used in JSX templates if they can be constructed without arguments.

Fragments​

To render multiple elements without a parent element, you can use a fragment:

import { Component } from "sinho";

export class HelloWorld extends Component("hello-world") {
render() {
return (
<>
<h1>Hello, World!</h1>
<p>This is a paragraph</p>
</>
);
}
}

Data Binding​

You can render dynamic data by using curly braces {} around a signal or expression:

import { Component, useSignal } from "sinho";

export class HelloWorld extends Component("hello-world") {
render() {
const [className, setClassName] = useSignal("hello-world");
const [name, setName] = useSignal("John");

return (
<div class={className}>
<h1>Hello, {name}</h1>
<p>
<button
onclick={() =>
setName((name) => (name === "John" ? "Jane" : "John"))
}
>
Switch User
</button>
</p>
</div>
);
}
}

This is equivalent to:

const [className, setClassName] = useSignal("hello-world");
const [name, setName] = useSignal("John");

return h.div({ class: className }, [
h.h1({}, ["Hello, ", name]),
h.p({}, "This is a paragraph"),
]);
warning

If you evaluate signals in templates, it will be considered a static value. To make it reactive, you need to use signals directly.

const [className, setClassName] = useSignal("hello-world");
const [name, setName] = useSignal("John");

// The following will **not** be reactive:

return (
<div class={className()}>
<h1>Hello, {name()}</h1>
<p>This is a paragraph</p>
</div>
);

JSX Attributes​

Properties vs. Attributes​

For functional components, JSX attributes will be passed as is to the function.

For native HTML tags and custom element nodes, Sinho will use a heuristic to determine if a JSX attribute is a property or an HTML attribute.

You can force using an attribute by using the attr: prefix for the JSX attribute name, and force using a property by using the prop: prefix.

return (
<input
attr:type="text"
prop:value={name}
/>
);

Events​

You can attach event listeners to elements by using the on prefix followed by the event name in camelCase:

<button
onclick={() => console.log("Clicked!")}
/>;

// Equivalent to:

buttonEl.addEventListener("click", () => console.log("Clicked!"));
<SimpleGreeting
onNameChange={() => console.log("Name changed!")}
/>;

// Equivalent to:

simpleGreeting.addEventListener("name-change", () => console.log("Clicked!"));

Sinho will convert the event name into kebab-case. Since native events usually do not include -, they will be written in lowercase. For events with unusual names, you can use the on: prefix followed by the verbatim event name:

<SimpleGreeting
on:name-change={() => console.log("Name changed!")}
/>
<div
on:DOMContentLoaded={() => console.log("Loaded")}
/>;

// Equivalent to:

divEl.addEventListener("DOMContentLoaded", () => console.log("Loaded"));

Get Reference to DOM Element​

You can get a reference to the underlying DOM element of a custom element or HTML tag by using useRef and attaching it to the ref attribute:

const inputRef = useRef<HTMLInputElement>();

return (
<input
ref={inputRef}
/>
);

In this example, inputRef is a signal that will hold the reference to the input element or null if the element is not (yet) rendered. The following effect will focus the input element whenever it is mounted:

useEffect(() => {
if (inputRef()) {
inputRef()!.focus();
}
});
note

Functional components do not have an underlying DOM element, so you cannot use ref on them.

Set HTML Content​

Sinho will generally render text children not as HTML but as plain text to prevent XSS attacks. If you want to render HTML content, you can use the dangerouslySetInnerHTML attribute:

import { Component, DangerousHtml } from "sinho";

class DangerousDiv extends Component("dangerous-div") {
render() {
const htmlContent: DangerousHtml = { __html: "<p><b>This is bold</b></p>" };

return (
<div
dangerouslySetInnerHTML={htmlContent}
/>
);
}
}
danger

If you use dangerouslySetInnerHTML, make sure that the content is safe and user input is sanitized. Otherwise, it can lead to XSS attacks.