Core Concepts

React integration

Bind rs-x expressions to React components with useRsxExpression and useRsxModel — components re-render automatically when model values change.

What it means

@rs-x/react provides two hooks that subscribe to rs-x expressions and trigger React re-renders when values change. useRsxExpression binds a single expression, while useRsxModel walks an entire model object and binds every scalar field. Both hooks clean up their subscriptions automatically when the component unmounts.

Practical value

You write plain model mutations — model.price = 99 — and every component that reads that field re-renders without any manual setState, useEffect subscription wiring, or context boilerplate. The expression engine handles fine-grained dependency tracking, so only the components that actually depend on a changed value re-render.

Key points

useRsxExpression — string expression example

Pass a model and an expression string. The component re-renders whenever firstName or lastName changes on the model.

import { useRsxExpression } from '@rs-x/react';

const model = {
  firstName: 'Jane',
  lastName: 'Doe',
};

function FullName() {
  // Re-renders automatically whenever firstName or lastName changes
  const fullName = useRsxExpression<string>('firstName + " " + lastName', { model });

  return <span>{fullName}</span>;
}

useRsxExpression — pre-built IExpression example

Build the expression once outside the component and share it. The hook subscribes to changes but does not dispose the expression on unmount.

import { rsx } from '@rs-x/expression-parser';
import { useRsxExpression } from '@rs-x/react';

// Create a shared expression once — outside the component
const model = { price: 100, quantity: 3 };
const totalExpr = rsx<number>('price * quantity')(model);

function OrderTotal() {
  // Pass the pre-built IExpression — no model needed
  const total = useRsxExpression(totalExpr);

  return <span>Total: {total}</span>;
}

// Mutate the model anywhere — OrderTotal re-renders automatically
model.quantity = 5;

useRsxExpression — leafWatchRule example

Narrow which array indices trigger a re-render by passing a leafWatchRule. Useful for large collections where you only care about specific items.

import { useRsxExpression } from '@rs-x/react';
import type { IIndexWatchRule } from '@rs-x/state-manager';

const model = {
  items: ['apple', 'banana', 'cherry'],
};

// Watch only index 0 — the component ignores changes to other indices
const watchFirstOnly: IIndexWatchRule = { indices: [0] };

function FirstItem() {
  const first = useRsxExpression<string>('items', {
    model,
    leafWatchRule: watchFirstOnly,
  });

  return <span>First item: {first?.[0]}</span>;
}

useRsxModel — full model binding example

Bind every scalar field in a model object. Each field is independently reactive — React only re-renders the subtree that depends on what changed.

import { useRsxModel } from '@rs-x/react';

const model = {
  user: {
    name: 'Alice',
    age: 30,
  },
  score: 95,
};

function UserCard() {
  // Recursively binds every scalar field — each field re-renders independently
  const { user, score } = useRsxModel<typeof model, typeof model>(model);

  return (
    <div>
      <p>{user.name}  age {user.age}</p>
      <p>Score: {score}</p>
    </div>
  );
}

useRsxModel — field filter example

Pass an optional FieldFilter predicate to exclude fields from binding. Useful for internal or non-reactive properties.

import { useRsxModel, type FieldFilter } from '@rs-x/react';

const model = {
  name: 'Alice',
  _internal: 'skip this',
  score: 95,
};

// Only bind fields that don't start with an underscore
const publicFieldsOnly: FieldFilter = (_parent, field) => !field.startsWith('_');

function UserCard() {
  const { name, score } = useRsxModel<typeof model, { name: string; score: number }>(
    model,
    publicFieldsOnly,
  );

  return (
    <p>{name}  {score}</p>
  );
}

getExpressionFactory example

Access the underlying IExpressionFactory singleton directly. Useful outside React components — for example in service files or utility hooks.

import { getExpressionFactory } from '@rs-x/react';

const model = { x: 10, y: 20 };

// Get the singleton factory — auto-initialises the rs-x module on first call
const factory = getExpressionFactory();
const sum = factory.create<number>(model, 'x + y');

sum.changed.subscribe(() => {
  console.log('sum:', sum.value); // logs whenever x or y changes
});

model.x = 42; // → logs "sum: 62"

getExpressionManager example

Access the IExpressionManager singleton to parse expressions without binding them to a model.

import { getExpressionManager } from '@rs-x/react';

const model = { a: 1, b: 2 };

const manager = getExpressionManager();

// Parse an expression without binding it to a model
const parsed = manager.parse('a + b');
console.log(parsed); // expression AST

Installation example

Install all required packages.

npm install @rs-x/core @rs-x/state-manager @rs-x/expression-parser @rs-x/react