Reactivity
UIX is reactive! That is because UIX utilizes the powerful DATEX Pointers under the hood. Pointers can contain any kind of JavaScript value, including strings, numbers, objects, arrays, functions and many more. DOM elements can also be bound to pointers, making them reactive.
UIX’s reactivity system can look like magic when you see it in action the first time. Take this simple app made with UIX:
const counter = $(0); setInterval(() => counter.val++, 1000); <div> <p>Counter = {counter}</p> <p>Counter + 1 = {counter + 1}</p> </div>;
Somehow UIX just knows that if the value of the counter changes, it should do these things:
- Update the counter in the first paragraph to the correct value
- Recalculate the expression
counter + 1
- Update the counter in the second paragraph to the expressions value
It’s obvious - this isn’t how JavaScript traditionally behaves. Let’s unfold the magic and see how UIX’s reactivity system works.
Consider this simple expression:
const myVar = 4; <div>{myVar + 5}</div>;
In plain JavaScript, this wouldn’t work without manual DOM updates. You would have to write explicit logic to update the DOM when the value of myVar changes. For example:
const myVar = 4; document.querySelector("#some-element").innerText = myVar + 5;
In regular JavaScript, the DOM is static unless you explicitly manipulate it with DOM APIs. This is where UIX introduces its powerful reactivity system, which automates the whole process of updating DOM based on state updates.
The Role of the “always” method
JSX in UIX can handle dynamic expressions and reactive updates using the always
method provided by DATEX:
const myVar = $(4); <div>always(() => myVar + 1)</div>;
This tells UIX to automatically update the div’s content whenever the value of the myVar
Ref changes. The expression inside always
creates a “smart transform” that updates the value and therefore the DOM when its dependencies change.
You can use the always
method to manually control reactivity when needed.
JUSIX: The module behind the “magic”
However, to make the developer experience even smoother, UIX automatically wraps certain expressions in always
calls. This eliminates the need for developers to write always
explicitly every time they want reactivity. This is how the counter
example at the start of this chapter is possible.
UIX uses the SWC transpiler to convert TypeScript and JSX code into plain JavaScript for both frontend and backend modules. Our custom SWC plugin called JUSIX handles the interpretation of JSX code as reactive JavaScript.
JUSIX is integrated in our custom version of Deno, called Deno for UIX, as part of the deno_ast
parser.
Additionally, the JUSIX WASM plugin transpiles the frontend code to plain JavaScript for the browser.
JUSIX uses the _$
method, which is essentially a shorthand for always
. It comes with optimizations and performance enhancements tailored to JSX.
For instance, a JSX expression like:
<p>Counter + 1 = {counter + 1}</p>;
is transpiled by JUSIX into the following JavaScript code:
<p>Counter + 1 = {_$(() => counter + 1)}</p>;
Reactivity examples
Reactive tenary statements allow updating element children based on logical conditions. These statements can be written like this:
const isLoggedIn = $(false); <div> <button onclick={() => (isLoggedIn.val = true)}>Click to login!</button> {isLoggedIn ? <HelloComponent /> : <span>Please login first</span>} </div>;
With JUSIX, the above code is transpiled to the following JavaScript code:
const isLoggedIn = $(false); <div> <button onclick={() => (isLoggedIn.val = true)}>Click to login!</button> {_$(() => isLoggedIn ? <HelloComponent /> : <span>Please login first</span>, )} </div>;
Reactivity for attributes
Reactivity does not only work for element children, but also for attribute values:
const counter = $(0); <button value={"Clicks:" + myValue} onclick={() => counter.val++} />;
is transpiled to:
const counter = $(0); <button value={_$(() => "Clicks:" + myValue)} onclick={() => counter.val++} />;
Reactive properties
To improve performance when updating properties of complex objects, such as arrays or JavaScript objects, DATEX propagates updates for an object’s pointer properties. JUSIX will optimize the handling of the updates to use special accessors instead of the always
call.
Properties of an object can be accessed using the prop(ref, key)
call.
const myForm = $({ name: "John" }); <input value={myForm.name} />;
will transpile to:
<input value={prop(myForm, "name")} />;
This will also work when using nested property access such as myComplexForm.user.name
and transpile to something like:
<input value={prop(prop(myComplexForm, "user"), "name")} />;
App configuration
JUSIX is optional. JUSIX can be activated by settings the compiler options jsxImportSource
property in the deno.json
to jusix
, or disabled when setting it to be uix
.
deno.json{ "importMap": "./importmap.json", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "jusix", // "uix" or "jusix" "lib": [ "dom", "deno.window" ] } }
When running UIX without having Deno for UIX installed, the app will terminate with an exception. To disable JUSIX or allow the UIX app to run with the original denoland/deno build, make sure to set the jsxImportSource
option to uix
. Keep in mind that this will disable all automatic reactivity features in your UIX project.
Help us improving our docs
Our documentations are fully open source. Something is wrong or unclear?