Skip to main content

Signal API Reference

Complete API documentation for Xote signals.

Type

type t<'a>

A signal is an opaque type representing a reactive state container. The type parameter 'a is the type of value the signal holds.

Functions

make

let make: 'a => t<'a>

Creates a new signal with an initial value.

Parameters:

  • initialValue: 'a - The initial value for the signal

Returns:

  • t<'a> - A new signal

Example:

let count = Signal.make(0)
let name = Signal.make("Alice")
let items = Signal.make([1, 2, 3])

get

let get: t<'a> => 'a

Reads the current value from a signal. When called inside a tracking context (effect or computed), automatically registers the signal as a dependency.

Parameters:

  • signal: t<'a> - The signal to read from

Returns:

  • 'a - The current value

Example:

let count = Signal.make(5)
let value = Signal.get(count) // Returns 5

Effect.run(() => {
// Creates a dependency on count
Console.log(Signal.get(count))
})

Note: Always creates a dependency when called in a tracking context. Use peek() to read without tracking.


peek

let peek: t<'a> => 'a

Reads the current value from a signal without creating a dependency, even in tracking contexts.

Parameters:

  • signal: t<'a> - The signal to read from

Returns:

  • 'a - The current value

Example:

let count = Signal.make(5)

Effect.run(() => {
// Does NOT create a dependency
let value = Signal.peek(count)
Console.log(value)
})

Signal.set(count, 10) // Effect will NOT re-run

Use cases:

  • Reading signals in effects without creating dependencies
  • Debugging (logging signal values without tracking)
  • Reading configuration values that don't need to trigger updates

set

let set: (t<'a>, 'a) => unit

Sets a new value for the signal and notifies all dependent observers.

Parameters:

  • signal: t<'a> - The signal to update
  • value: 'a - The new value

Returns:

  • unit

Example:

let count = Signal.make(0)
Signal.set(count, 10) // count is now 10

Important: Always notifies dependents, even if the new value equals the old value. There is no built-in equality check.


update

let update: (t<'a>, 'a => 'a) => unit

Updates a signal's value based on its current value.

Parameters:

  • signal: t<'a> - The signal to update
  • fn: 'a => 'a - Function that receives the current value and returns the new value

Returns:

  • unit

Example:

let count = Signal.make(0)
Signal.update(count, n => n + 1) // count is now 1
Signal.update(count, n => n * 2) // count is now 2

let items = Signal.make([1, 2, 3])
Signal.update(items, arr => Array.concat(arr, [4, 5])) // [1, 2, 3, 4, 5]

Note: Equivalent to Signal.set(signal, fn(Signal.get(signal))) but more concise.


Examples

Basic Usage

open Xote

let count = Signal.make(0)

// Read
Console.log(Signal.get(count)) // 0

// Update
Signal.set(count, 5)
Console.log(Signal.get(count)) // 5

// Update based on current value
Signal.update(count, n => n + 1)
Console.log(Signal.get(count)) // 6

With Effects

let count = Signal.make(0)

Effect.run(() => {
Console.log2("Count changed:", Signal.get(count))
})

Signal.set(count, 1) // Logs: "Count changed: 1"
Signal.set(count, 2) // Logs: "Count changed: 2"

With Computed

let count = Signal.make(5)
let doubled = Computed.make(() => Signal.get(count) * 2)

Console.log(Signal.get(doubled)) // 10

Signal.set(count, 10)
Console.log(Signal.get(doubled)) // 20

Complex State

type user = {
id: int,
name: string,
email: string,
}

let user = Signal.make({
id: 1,
name: "Alice",
email: "alice@example.com",
})

// Update specific fields
Signal.update(user, u => {...u, name: "Alice Smith"})
Signal.update(user, u => {...u, email: "alice.smith@example.com"})

Array Operations

let todos = Signal.make([])

// Add item
Signal.update(todos, arr => Array.concat(arr, ["Buy milk"]))

// Remove item
Signal.update(todos, arr => Array.filter(arr, item => item != "Buy milk"))

// Update item
Signal.update(todos, arr =>
Array.map(arr, item =>
item == "Buy milk" ? "Buy oat milk" : item
)
)

Notes

  • Signals always notify dependents on set(), even if the value didn't change
  • Use peek() to avoid creating dependencies in effects
  • Signals work with any type: primitives, records, arrays, etc.
  • Signal updates are synchronous by default (use Core.batch() for grouping)

See Also