react-json-logic
v3.1.3
Published
Build and evaluate JsonLogic rules with React components.
Maintainers
Readme
react-json-logic
Build and evaluate JsonLogic rules with React components.
Headless. No CSS shipped; bring your own. Built on Base UI primitives. Style with Tailwind, CSS modules, vanilla CSS, or whatever you like.
Install
pnpm add react-json-logic react react-domRequires React 19 (react / react-dom peer ^19.0.0).
Usage
import { useState } from "react";
import JsonLogicBuilder, { applyLogic, type JsonLogicValue } from "react-json-logic";
function Example() {
const [rule, setRule] = useState<JsonLogicValue>("");
const data = { user: { age: 21 } };
return (
<>
<JsonLogicBuilder value={rule} data={data} onChange={setRule} />
<p>Result: {String(applyLogic(rule, data))}</p>
</>
);
}Props
| Prop | Type | Default | Description |
| ------------- | ------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------- |
| onChange | (value: JsonLogicValue) => void | none | Called with the updated rule whenever the builder changes. |
| value | JsonLogicValue | "" | Current rule (controlled). |
| data | JsonLogicData \| string | {} | Sample data, used by accessor (var) field suggestions. Pass an object/array directly or a JSON string (parsed for you). |
| onDataError | (err: unknown, raw: string) => void | console.warn | Called when data is a string and JSON.parse fails. De-duplicated by raw value, fires once per malformed input. |
JsonLogicValue is the recursive any-shape type for rules and primitive
values. JsonLogicData = Record<string, unknown> | unknown[] is the
narrower shape the data prop accepts (objects or arrays; primitives
make no sense as accessor data).
Named exports: applyLogic, rule, validate, OPERATORS, FIELD_TYPES, types JsonLogicBuilderProps, FieldType, Operator, JsonLogicValue, JsonLogicData, ValidationError, ValidationResult.
Building rules in code
rule is a typed factory you can use to construct JsonLogic rules without writing the JSON shape by hand:
import { applyLogic, rule, validate } from "react-json-logic";
const r = rule.and(rule.eq(rule.var("user.age"), 21), rule.gt(rule.var("score"), 100));
applyLogic(r, { user: { age: 21 }, score: 150 }); // → true
validate(r); // → { ok: true }Each factory returns a JsonLogicValue shaped per the canonical JsonLogic spec; the <JsonLogicBuilder /> UI, applyLogic, and validate all consume the same shape. Fixed-arity factories enforce argument counts through their TypeScript signatures. Variadic factories accept any number of arguments; call validate() to check their evaluator arity.
| Group | Factories |
| ------------ | -------------------------------------------------------------------- |
| Equality | eq, looseEq, notEq, looseNotEq |
| Logical | and, or, not, if(...args) |
| Comparison | lt, lte, gt, gte |
| Arithmetic | add, sub, mul, div, mod, min, max |
| Accessor | var(path, fallback?), missing(...keys), missingSome(min, keys) |
| String/Array | in(needle, haystack), cat(...args), merge(...args) |
| Higher-order | some, all, none, map, filter |
validate(rule) reports structural problems (multi-key operator objects and arity violations) as { ok: false, errors: [{ path, message }] }. Variadic arity follows json-logic-js: there is no editor-sized maximum, and empty argument lists are accepted except for multiplication, which requires at least one operand. Empty and/or return undefined, min/max return positive/negative infinity, and if returns null; validation does not guarantee a finite or JSON-serializable result. Fixed-arity operators still check their declared argument counts. Custom operators (registered via json-logic-js's add_operation) are tolerated; only known operators get arity-checked.
OPERATORS[].fieldCount governs the visual editor's add/remove controls and rendered fields. Rules larger than those editing limits can still validate and evaluate. For example, rule.add() validates and evaluates to 0, and rule.add(...Array(101).fill(1)) validates and evaluates to 101.
Styling
Components render unstyled. Target the rendered DOM via stable data-rjl-* attributes:
| Attribute | What it marks |
| ------------------------------- | ---------------------------------------------------------------------- |
| data-rjl-builder | Outermost wrapper |
| data-rjl-any | Recursive operator container |
| data-rjl-children | Wrapper around an operator's child fields |
| data-rjl-field | A single child field row |
| data-rjl-add | "Add field" button |
| data-rjl-remove | "Remove field" button |
| data-rjl-operator-trigger | Operator dropdown trigger |
| data-rjl-operator-popup | Operator dropdown popup (portaled) |
| data-rjl-input | Value-input wrapper |
| data-rjl-input-type-trigger | Type select trigger (text / number / boolean / null / array) |
| data-rjl-input-type-popup | Type select popup (portaled) |
| data-rjl-input-value | The text/number <input>, or the array <textarea> |
| data-rjl-input-boolean | Boolean true / false select trigger |
| data-rjl-input-boolean-popup | Boolean true / false select popup (portaled) |
| data-rjl-input-array | Array-literal <textarea> |
| data-rjl-accessor | Accessor (var) wrapper |
| data-rjl-accessor-level | One level of an accessor path |
| data-rjl-accessor-level-index | The level's index ("0", "1", …) |
| data-rjl-accessor-input | Accessor input element |
| data-rjl-accessor-popup | Accessor suggestion popup (portaled) |
| data-rjl-higher-order | some / all / none / map / filter wrapper |
| data-rjl-higher-order-arrow | The => glyph |
| data-rjl-higher-order-child | The inner expression of a higher-order op |
Example with Tailwind:
[data-rjl-operator-trigger] {
@apply inline-flex h-8 items-center rounded border px-2;
}
[data-rjl-operator-popup] {
@apply rounded border bg-white shadow;
}
[data-rjl-add],
[data-rjl-remove] {
@apply h-6 w-6 rounded border bg-white text-sm;
}Development
This repo runs on Vite+. Bootstrap with pnpm and invoke the repository-local Vite+ binary explicitly:
pnpm install --frozen-lockfile # install deps
pnpm exec vp check # format + lint + typecheck
pnpm exec vp test # run tests
pnpm exec vp test --coverage # with coverage report
pnpm exec vp pack # build the library
pnpm verify # the full gate (check + test --coverage + pack)See AGENTS.md for project layout and contributor notes.
License
MIT
