Skip to content

Reactive JSX Patterns

Reactivity in @hedystia/view comes from wrapping expressions in functions. The JSX runtime detects function children and function props, creating effects that update only the specific DOM nodes that need to change.

Static vs Reactive

The critical distinction: JSX expressions must be functions to be reactive.

tsx
import { sig, val } from "@hedystia/view";

const count = sig(0);
const [c] = sig(0);

// ❌ STATIC — values are read once at render time
<span>{val(count)}</span>
<span>{c()}</span>

// ✅ REACTIVE — functions are wrapped in effects
<span>{() => val(count)}</span>
<span>{c}</span>

The first examples read the signal at component execution time and insert the resulting value as a static text node.

The reactive examples pass a function. The JSX runtime detects function children and creates an effect that re-runs the function whenever its dependencies change. Since a destructured signal accessor (like c) is already a function () => T, you can pass it directly.

Note: If you need to perform an operation on the value, you must wrap it in a new arrow function: <span>{() => c() + 1}</span>. Passing {c() + 1} would pass a static number.

Reactive Text

Use a signal accessor or a function child to create reactive text content:

tsx
import { sig, mount } from "@hedystia/view";

function Greeting() {
  const [name, setName] = sig("world");

  return (
    <div>
      <h1>Hello, {name}!</h1>
      <input
        value={name}
        onInput={(e) => setName((e.target as HTMLInputElement).value)}
      />
    </div>
  );
}

mount(Greeting, document.getElementById("root")!);


</div>

## Reactive Style

Pass a function to `style` to make it reactive:

<div v-pre>

```tsx
import { sig, mount } from "@hedystia/view";

function ToggleColor() {
  const [active, setActive] = sig(false);

  return (
    <div
      style={() => ({
        color: active() ? "green" : "gray",
        fontWeight: active() ? "bold" : "normal",
      })}
      onClick={() => setActive(!active())}
    >
      {() => active() ? "Active" : "Inactive"}
    </div>
  );
}

mount(ToggleColor, document.getElementById("root")!);

Reactive Props

Any non-event prop that receives a function becomes reactive:

tsx
import { sig, mount } from "@hedystia/view";

function DynamicInput() {
  const [text, setText] = sig("");

  return (
    <div>
      <input
        value={text}
        onInput={(e) => setText((e.target as HTMLInputElement).value)}
      />
      <p class={() => text().length > 10 ? "long" : "short"}>
        {() => text().length} characters
      </p>
    </div>
  );
}

mount(DynamicInput, document.getElementById("root")!);

Reactive List

Return an array from a function child to render a reactive list:

tsx
import { sig, val, set, mount } from "@hedystia/view";

function TodoList() {
  const [items, setItems] = sig(["Buy milk", "Walk dog"]);

  const addItem = () => {
    setItems((curr) => [...curr, `Item ${curr.length + 1}`]);
  };

  return (
    <div>
      <ul>
        {() => items().map((item) => <li>{item}</li>)}
      </ul>
      <button onClick={addItem}>Add</button>
    </div>
  );
}

mount(TodoList, document.getElementById("root")!);

For keyed, efficient list rendering, use the For component instead.

Reactive Conditional

Return different elements from a function child for conditional rendering:

tsx
import { sig, mount } from "@hedystia/view";

function Toggle() {
  const [show, setShow] = sig(true);

  return (
    <div>
      <button onClick={() => setShow(!show())}>Toggle</button>
      {() => show()
        ? <p>Content is visible</p>
        : <p style={{ color: "gray" }}>Content is hidden</p>
      }
    </div>
  );
}

mount(Toggle, document.getElementById("root")!);

For cleaner conditional rendering, use the Show component.

Summary

PatternSyntaxCreates Effect?
Static text{val(count)} or {c()}No — read once
Reactive text{() => val(count)} or {c}Yes
Static stylestyle={ { color: "red" }}No
Reactive stylestyle={() => ({ color: c() })}Yes
Static propvalue={val(text)}No — set once
Reactive propvalue={text}Yes
Reactive list{() => items().map(...)}Yes
Reactive cond{() => show() ? <A /> : <B />}Yes