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" });
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"),
]);
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();
}
});
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}
/>
);
}
}
If you use dangerouslySetInnerHTML
, make sure that the content is safe and
user input is sanitized. Otherwise, it can lead to XSS attacks.