@molecule/app-rule-builder-react

Feature · rule-builder · App (browser) · v1.0.2 · Apache-2.0

Visual condition / rule builder — nested AND / OR groups of field-operator-value leaves, used for feature-flag targeting, automation triggers, segmentation, and IoT scenarios

npm install @molecule/app-rule-builder-react

npm · Source on GitHub

How it works

@molecule/app-rule-builder-react is a ready-made rule-builder feature for the app (browser) side. It composes the core interfaces it needs, so it works with whichever providers your app has bonded.

import { useState } from 'react'

import {
  RuleBuilder,
  emptyGroup,
  type Rule,
  type RuleSchema,
} from '@molecule/app-rule-builder-react'

const schema: RuleSchema = {
  fields: [
    {
      name: 'country',
      label: 'Country',
      type: 'select',
      options: [{ value: 'US', label: 'United States' }],
    },
    { name: 'plan', label: 'Plan', type: 'text' },
    { name: 'spend', label: 'Spend', type: 'number' },
  ],
}

function Demo() {
  const [rules, setRules] = useState<Rule>(emptyGroup('AND'))
  return <RuleBuilder schema={schema} rules={rules} onChange={setRules} />
}

Works with: @molecule/app-react, @molecule/app-ui, @molecule/app-ui-react

Reference

Visual condition / rule builder for React.

Exports:

  • <RuleBuilder> — controlled tree editor for AND / OR groups of field-operator-value leaves.
  • RuleSchema, RuleField, RuleOperator, Rule, RuleGroup, RuleLeaf, RuleLeafValue, RuleGroupOp, RuleFieldType types.
  • defaultOperators, operatorsFor, operatorById, OP operator catalog helpers.
  • emptyGroup, emptyLeaf, replaceById, removeById, appendToGroup, toggleGroupOp, makeId pure tree utilities.

Used by feature-flag-manager (audience targeting), ai-workflow-automator (trigger conditions), email-marketing (segments), iot-device-manager + smart-home-dashboard (scenes / automations).

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

Quick Start

import { useState } from 'react'

import {
  RuleBuilder,
  emptyGroup,
  type Rule,
  type RuleSchema,
} from '@molecule/app-rule-builder-react'

const schema: RuleSchema = {
  fields: [
    {
      name: 'country',
      label: 'Country',
      type: 'select',
      options: [{ value: 'US', label: 'United States' }],
    },
    { name: 'plan', label: 'Plan', type: 'text' },
    { name: 'spend', label: 'Spend', type: 'number' },
  ],
}

function Demo() {
  const [rules, setRules] = useState<Rule>(emptyGroup('AND'))
  return <RuleBuilder schema={schema} rules={rules} onChange={setRules} />
}

Type

feature

Installation

npm install @molecule/app-rule-builder-react @molecule/app-react @molecule/app-ui @molecule/app-ui-react react
npm install -D @types/react

API

Interfaces

RuleBuilderProps

Public component props.

interface RuleBuilderProps {
  /** Field catalog the user picks from. */
  schema: RuleSchema
  /** Current rule tree (controlled). */
  rules: Rule
  /**
   * Called whenever the user mutates the tree — receives the full new
   * root rule. Output is a deterministic JSON-serializable structure.
   */
  onChange: (next: Rule) => void
  /** Optional extra root-level class. */
  className?: string
}

RuleOperator

Operator descriptor — id is what's stored on the leaf, label is what's shown in the operator dropdown.

arity controls how many value inputs are rendered:

  • 'unary' — no value input (e.g. is empty, exists)
  • 'binary' — one value input (default)
  • 'between' — two value inputs (rendered as from/to)
interface RuleOperator {
  /** Operator id stored on the leaf. */
  id: string
  /** Translated, human-readable label. */
  label: string
  /** Number of value inputs to render. Default `'binary'`. */
  arity?: 'unary' | 'binary' | 'between'
}

RuleField

Field descriptor in the schema — the rule builder uses it to render the field dropdown, infer the value editor, and (when no explicit operators list is given) pick a default operator catalog by type.

interface RuleField {
  /** Stable field identifier stored on the leaf. */
  name: string
  /** Translated, human-readable label shown in the field dropdown. */
  label: string
  /** Built-in type used to pick the value editor + default operators. */
  type: RuleFieldType
  /**
   * Operators valid for this field. Optional — when omitted, the
   * builder uses the default catalog for `type` from `defaultOperators`.
   */
  operators?: RuleOperator[]
  /**
   * For `type: 'select'` — the option list rendered as a `<Select>`.
   * Ignored for other types.
   */
  options?: Array<{ value: string; label: string }>
  /** Optional placeholder for text/number/date inputs. */
  placeholder?: string
}

RuleSchema

Schema given to <RuleBuilder>. Currently only fields — kept as an object so future schema-level config (max depth, group limit, etc.) can be added without breaking the API.

interface RuleSchema {
  /** Available fields, in the order they appear in the field dropdown. */
  fields: RuleField[]
}

RuleLeaf

Leaf rule — a single field op value predicate.

value is opaque to the builder for text / number / select / boolean (it stores whatever the input produced — string for text / select / date, number for number, boolean for boolean). For between arity operators it's stored as a [from, to] two-element tuple.

interface RuleLeaf {
  /** Discriminator. */
  kind: 'leaf'
  /** Stable id (uuid-ish) — used for React keys + delete identity. */
  id: string
  /** Field name from the schema. Empty string when unset. */
  field: string
  /** Operator id from the field's operator catalog. Empty when unset. */
  op: string
  /**
   * Current value. `undefined` when unset. Encoding depends on the
   * operator's `arity`:
   * - `'unary'` — value is ignored.
   * - `'binary'` — string | number | boolean.
   * - `'between'` — `[from, to]` tuple.
   */
  value?: RuleLeafValue
}

RuleGroup

Group rule — a logical conjunction (AND) or disjunction (OR) of children. Children may themselves be groups, allowing arbitrary nesting like (A AND (B OR C) AND D).

interface RuleGroup {
  /** Discriminator. */
  kind: 'group'
  /** Stable id. */
  id: string
  /** Logical operator joining the children. */
  op: RuleGroupOp
  /** Child rules — groups or leaves, in display order. */
  children: Rule[]
}

Types

RuleGroupOp

Logical join operator at a group level.

type RuleGroupOp = 'AND' | 'OR'

RuleFieldType

Built-in field types supported by the default operator catalog and value editors. Consumers can supply custom operator lists per field if these don't fit.

type RuleFieldType = 'text' | 'number' | 'date' | 'select' | 'boolean'

RuleLeafValue

Possible leaf value encodings. See RuleLeaf.value.

type RuleLeafValue = string | number | boolean | [string, string] | [number, number]

Rule

Either a group or a leaf.

type Rule = RuleGroup | RuleLeaf

Functions

RuleBuilder(props)

Visual rule / predicate builder. The rendered tree is a recursive RuleGroup of RuleLeaf predicates joined by AND or OR. The component is fully controlled — every interaction emits a new root rule via onChange.

function RuleBuilder({ schema, rules, onChange, className }: RuleBuilderProps): ReactNode
  • props — Component props.

Returns: The rendered rule builder.

operatorsFor(field)

Resolve the operator catalog for field. Returns the field's own operators override when provided, otherwise the built-in catalog for the field's type.

function operatorsFor(field: RuleField): RuleOperator[]
  • field — Field descriptor from the schema.

Returns: Operator catalog applicable to this field.

operatorById(field, opId)

Look up a single operator descriptor by id within field's catalog.

function operatorById(field: RuleField, opId: string): RuleOperator | undefined
  • field — Field descriptor.
  • opId — Operator id to look up.

Returns: The operator descriptor, or undefined if opId isn't in the catalog.

makeId()

Generate a short non-cryptographic id. Sufficient for React keys and in-memory rule identity within a single editing session.

function makeId(): string

Returns: A short random id.

emptyLeaf()

Build a fresh empty leaf — used when the user clicks "Add condition".

function emptyLeaf(): RuleLeaf

Returns: A new leaf with all fields blank and a fresh id.

emptyGroup(op)

Build a fresh empty group — used when the user clicks "Add group".

function emptyGroup(op?: RuleGroupOp): RuleGroup
  • op — Initial group operator, defaults to 'AND'.

Returns: A new group with one empty leaf already inside.

replaceById(tree, targetId, replacement)

Replace target (matched by id) anywhere in the tree with replacement.

function replaceById(tree: Rule, targetId: string, replacement: Rule): Rule
  • tree — Root of the rule tree.
  • targetId — Id of the rule to replace.
  • replacement — Replacement rule (same id is not required).

Returns: New tree with the replacement applied.

removeById(tree, targetId)

Remove the rule with id targetId from the tree. The root itself cannot be removed — when targetId matches the root, the tree is returned unchanged.

function removeById(tree: Rule, targetId: string): Rule
  • tree — Root of the rule tree.
  • targetId — Id of the rule to remove.

Returns: New tree with the rule removed (or the original tree if no match).

appendToGroup(tree, groupId, child)

Append child to the children of the group identified by groupId. If the id doesn't match any group, returns the tree unchanged.

function appendToGroup(tree: Rule, groupId: string, child: Rule): Rule
  • tree — Root of the rule tree.
  • groupId — Id of the group to append to.
  • child — Rule to append.

Returns: New tree with the child appended.

toggleGroupOp(tree, groupId)

Toggle a group's op between 'AND' and 'OR'.

function toggleGroupOp(tree: Rule, groupId: string): Rule
  • tree — Root of the rule tree.
  • groupId — Id of the group to toggle.

Returns: New tree with the group's op flipped (or the original tree if no match).

Constants

OP

Operator id constants — exported so consumers can inspect leaves.

const OP: {
  readonly Equals: 'eq'
  readonly NotEquals: 'neq'
  readonly Contains: 'contains'
  readonly NotContains: 'notContains'
  readonly StartsWith: 'startsWith'
  readonly EndsWith: 'endsWith'
  readonly GreaterThan: 'gt'
  readonly GreaterThanOrEqual: 'gte'
  readonly LessThan: 'lt'
  readonly LessThanOrEqual: 'lte'
  readonly Between: 'between'
  readonly Before: 'before'
  readonly After: 'after'
  readonly IsTrue: 'isTrue'
  readonly IsFalse: 'isFalse'
  readonly IsEmpty: 'isEmpty'
  readonly IsNotEmpty: 'isNotEmpty'
  readonly In: 'in'
  readonly NotIn: 'notIn'
}

defaultOperators

Untranslated operator descriptors per field type.

RuleBuilder translates each label at render time; the strings here are English fallbacks only, never shown directly.

const defaultOperators: Record<RuleFieldType, RuleOperator[]>

Injection Notes

Requirements

Peer dependencies:

  • @molecule/app-react ^1.0.1
  • @molecule/app-ui ^1.0.1
  • @molecule/app-ui-react ^1.0.1
  • react ^18.0.0 || ^19.0.0

Runtime Dependencies

  • @molecule/app-react

  • @molecule/app-ui

  • @molecule/app-ui-react

  • react

  • <RuleBuilder> throws unless rendered inside <I18nProvider> (from @molecule/app-react) with a bonded ClassMap (setClassMap()).

  • Output is a plain JSON-serializable Rule tree — this package only renders and edits it. Evaluation (matching rows/users against the tree) is the app's job, typically server-side.

  • Fully controlled: keep the tree in state and pass it back via rules; start with emptyGroup('AND').

  • Translations: registered companion bond @molecule/app-locales-rule-builder.

Translations

Translation strings are provided by @molecule/app-locales-rule-builder.