Skip to content

This guide assumes that you have basic JavaScript knowledge. If not, MDN is a good place to start.

Introduction

To develop a rvx application locally, you need a recent version of NodeJS or any other compatible JavaScript runtime.

You can use the commands below to setup a minimal rvx project using Vite and TypeScript:

# Create a "my-app" directory from rvx's "vite-ts" template project:
npx degit mxjp/rvx/templates/vite-ts my-app

# Move into "my-app":
cd my-app

# Install dependencies:
npm install

# Start a development server:
npm start

Entry Point

After setting up the quick start template, you can find the main entry point in src/main.tsx:

import { mount } from "rvx";

mount(
    document.body,
    <h1>Hello World!</h1>
);
The <h1>Hello World!</h1> expression directly creates an element and the mount function takes whatever content is supported by rvx and appends it to the document body.

State & Reactivity

Reactivity is entirely based on signals which are objects that hold an arbitrary value:

import { sig } from "rvx";

// Create a signal with the initial value 0:
const count = sig(0);
When a signal is used directly or it's value is accessed through a function call, the signal can notify it's dependants when the value changes:
import { mount, sig } from "rvx";

const count = sig(0);

mount(
    document.body,
    <>
        {/* Using the signal directly is reactive: */}
        Current count: {count}

        {/* Accessing it's value through a function is reactive: */}
        Is even count: {() => (count.value & 1) === 0}

        {/*
            Using the value directly is not reactive because
            rvx has no way of re-evaluating the expression:
        */}
        Initial count: {count.value}
    </>
);
To replace a signal value, you can set the value property:
count.value = 1;
To update an object, you can use the update function.
const values = sig([7, 42]);

// This will modify the inner value and then notify dependants:
values.update(values => {
    values.push(77);
});

// Note, that deeply modifying objects directly does nothing:
values.value.push(77);

Basic Rendering

JSX expressions directly create HTML elements:

<button>Click me!</button>

By default, attribute names are the same as in HTML:

<img alt="An image" src="..." />

Attributes, set to false, undefined or null are removed. true is set as an empty string:

<input disabled={false} /> // <input>
<input disabled={true} /> // <input disabled="">

Attributes prefixed with prop: are set using JavaScript properties:

<input type="text" prop:value="Hello World!" />

Event Listeners

Attributes prefixed with on: are added as event listeners.

<button on:click={event => {
    console.log("Clicked", event.target);
}}>Click me!</button>

Conditional Rendering

To render conditional or repeated content rvx uses so called Views which are sequences of DOM nodes that can change over time.

The Show component renders content when a condition is met:

import { mount, sig, Show } from "rvx";

const showMessage = sig(false);

mount(
    document.body,
    <>
        <button on:click={() => { showMessage.value = !showMessage.value }}>
            Toggle message
        </button>

        <Show when={showMessage}>
            {() => <h1>Hello World!</h1>}
        </Show>
    </>
);

The For component repeats content for each unique item in an iterable:

import { mount, sig, For } from "rvx";

const values = sig([]);

mount(
    document.body,
    <>
        <button on:click={() => { values.update(v => v.push(Date.now())) }}>
            Add current time
        </button>

        <ul>
            <For each={values}>
                {value => <li>{value}</li>}
            </For>
        </ul>
    </>
);

In addition to Show and For, rvx provides some more view types you can find in the API documentation or you can implement your own views for special use cases.

Components

Components in rvx are just functions that return arbitrary content. They are called once when the component is rendered.

function Message() {
    return <h1>Hello World!</h1>;
}

// Using the component:
<Message />;

Properties are passed as is via the props argument. Properties are static by default.

function Message(props: { message: string; }) {
    return <h1>{props.message}</h1>;
}

// Using the component:
<Message message="Hello World!" />;

To make properties reactive, you can use the Expression type which can be a static value, a signal or a function:

import { Expression } from "rvx";

function Message(props: { message: Expression<string>; }) {
    return <h1>{props.message}</h1>;
}

// Using the component:
<Message message="Hello World!" />;
<Message message={() => "Hello World!"} />;
<Message message={someSignal} />;

To compute something from an expression value or evaluate it, you can use the map and get functions:

import { Expression, map, get } from "rvx";

function Message(props: { message: Expression<string>; }) {
    console.log("Initial message:", get(props.message));
    return <h1>{map(props.message, m => m.toUpperCase())}</h1>;
}

To allow components to update a value, you can use the Signal type:

import { mount, sig, Signal } from "rvx";

function TextInput(props: { value: Signal<string>; }) {
    return <input
        type="text"
        prop:value={props.value}
        on:input={event => {
            props.value.value = (event.target as HTMLInputElement).value;
        }}
    />;
}

const text = sig("Hello World!");
<TextInput value={text} />;

Component children are passed via the children property:

function Message(props: { children?: unknown; }) {
    return <h1>{props.children}</h1>;
}

<Message>Hello World!</Message>;