use-logic-rules
v0.1.4
Published
A powerful React hook for reactive rule-based state management with automatic side effects
Maintainers
Readme
use-logic-rules
A powerful React hook for reactive rule-based state management with automatic side effects. Built with TypeScript and powered by signals for optimal performance.
Features
- 🎯 Rule-based state management - Define business logic as declarative rules
- ⚡ Automatic side effects - State updates trigger side effects automatically
- 🔄 Reactive signals - Built on
react-set-signalfor fine-grained reactivity - 📦 TypeScript first - Full type safety with automatic type inference
- 🪶 Lightweight - Minimal bundle size
- 🎨 Flexible - Works with or without reducers
Installation
npm install use-logic-rules react-set-signal
# or
pnpm add use-logic-rules react-set-signal
# or
yarn add use-logic-rules react-set-signalBasic Usage
import { useLogicRules, Rules } from 'use-logic-rules'
// Define your fact type
interface MyFact {
count: number
isEven: boolean
message: string
}
// Define initial fact
const initialFact: MyFact = {
count: 0,
isEven: true,
message: ''
}
// Define rules with proper typing
const rules: Rules<MyFact> = {
countIsEven: (fact) => fact.count % 2 === 0,
countIsPositive: (fact) => fact.count > 0,
}
// Define side effects
const sideEffects = {
countIsEven: (fact: MyFact, ruleValues) => {
return {
isEven: ruleValues.countIsEven,
message: ruleValues.countIsEven ? 'Even number!' : 'Odd number!'
}
}
}
function Counter() {
const { fact, state, setFact } = useLogicRules({
initialFact,
rules,
sideEffects
})
return (
<div>
<p>Count: {fact.count}</p>
<p>Message: {fact.message}</p>
<p>Is Even: {state.countIsEven ? 'Yes' : 'No'}</p>
<button onClick={() => setFact(f => { f.count++ })}>
Increment
</button>
</div>
)
}Inline Rules (Best IntelliSense)
For the best TypeScript IntelliSense experience, define rules inline:
const { state, fact, setFact } = useLogicRules({
initialFact: { count: 0, isEven: true, message: '' },
rules: {
countIsEven: (fact) => fact.count % 2 === 0, // fact is automatically typed!
countIsPositive: (fact) => fact.count > 0,
}
})
// state.countIsEven and state.countIsPositive are fully typed!Conditional Rules
You can define conditional rules that only evaluate when a condition is met:
const rules: Rules<MyFact> = {
countIsEven: (fact) => fact.count % 2 === 0,
conditionalRule: {
when: (fact) => fact.count > 0,
then: {
isPositive: () => true,
isLarge: (fact) => fact.count > 100
}
}
}
// The state will include: countIsEven, isPositive, isLarge
// (flattened from nested conditional rules)Using Reducers
You can use reducers to define reusable actions that modify the fact state.
Function Reducer
import { useLogicRules, createReducer } from 'use-logic-rules'
const initialFact = {
count: 0,
alarmValue: null as number | null,
}
// Function reducer returns an object of actions
const reducer = createReducer(initialFact, (fact) => ({
increment: () => fact.set(f => { f.count++ }),
decrement: () => fact.set(f => { f.count-- }),
setAlarm: (value: number) => fact.set(f => { f.alarmValue = value }),
reset: () => fact.set({ count: 0, alarmValue: null })
}))
function Counter() {
const { fact, state, actions } = useLogicRules({
initialFact,
rules,
reducer
}, { useReducer: true })
return (
<div>
<p>Count: {fact.count}</p>
<button onClick={actions.increment}>+</button>
<button onClick={actions.decrement}>-</button>
<button onClick={() => actions.setAlarm(10)}>Set Alarm</button>
<button onClick={actions.reset}>Reset</button>
</div>
)
}Object Reducer
Alternatively, define actions as an object where each action receives fact as the first argument:
import { useLogicRules, createActions } from 'use-logic-rules'
const initialFact = {
count: 0,
alarmValue: null as number | null,
}
// Object reducer: each action receives fact as first argument
const reducer = createActions(initialFact, {
increment: (fact) => fact.set(f => { f.count++ }),
decrement: (fact) => fact.set(f => { f.count-- }),
setAlarm: (fact, value: number) => fact.set(f => { f.alarmValue = value }),
reset: (fact) => fact.set({ count: 0, alarmValue: null })
})
function Counter() {
const { fact, actions } = useLogicRules({
initialFact,
rules,
reducer
}, { useReducer: true })
// Actions are called without the fact argument
return (
<div>
<button onClick={actions.increment}>+</button>
<button onClick={() => actions.setAlarm(10)}>Set Alarm</button>
</div>
)
}API
useLogicRules(params, options?)
Parameters
params: ObjectinitialFact: Initial state of your factsrules: Rule definitions objectsideEffects: Side effect handlers (optional)reducer: Optional reducer (function or object) for actions
options:UseLogicRulesOptions(optional)debug: Enable debug logging (default:false)useReducer: Enable reducer mode (default:false)delay: Delay for side effects in ms (default:0)
Returns
fact: Current fact state (reactive)state: Current rule values as boolean record (reactive)setFact: Function to update facts$fact: Signal for fact state$ruleValues: Signal for rule valuesactions: Actions object (when using reducer)
Helper Functions
createRules(initialFact, rules)
Create rules with automatic type inference for the fact parameter. Works in both JS and TypeScript:
import { useLogicRules, createRules } from 'use-logic-rules'
const initialFact = { count: 0, name: '' }
// fact is automatically typed based on initialFact!
const rules = createRules(initialFact, {
countIsEven: (fact) => fact.count % 2 === 0,
hasName: (fact) => fact.name.length > 0,
})
const { state } = useLogicRules({ initialFact, rules })createReducer(initialFact, reducerFn)
Create a function reducer with automatic type inference:
import { createReducer } from 'use-logic-rules'
const initialFact = { count: 0 }
const reducer = createReducer(initialFact, (fact) => ({
increment: () => fact.set(f => { f.count++ }),
add: (amount: number) => fact.set(f => { f.count += amount }),
}))createActions(initialFact, actions)
Create an object reducer with automatic type inference:
import { createActions } from 'use-logic-rules'
const initialFact = { count: 0 }
const reducer = createActions(initialFact, {
increment: (fact) => fact.set(f => { f.count++ }),
add: (fact, amount: number) => fact.set(f => { f.count += amount }),
})defineRules<TFact>()
TypeScript-only helper for defining rules with explicit type parameter:
import { defineRules } from 'use-logic-rules'
interface MyFact {
count: number
}
const buildRules = defineRules<MyFact>()
const rules = buildRules({
countIsEven: (fact) => fact.count % 2 === 0,
})createRulesBuilder(initialFact)
Curried version of createRules:
import { createRulesBuilder } from 'use-logic-rules'
const initialFact = { count: 0 }
const buildRules = createRulesBuilder(initialFact)
const rules = buildRules({
countIsEven: (fact) => fact.count % 2 === 0,
})TypeScript Types
The library exports the following user-facing types:
Rules<TFact>
Main type for defining rules objects:
import type { Rules } from 'use-logic-rules'
interface MyFact {
count: number
name: string
}
const rules: Rules<MyFact> = {
countIsPositive: (fact) => fact.count > 0,
hasName: (fact) => fact.name.length > 0,
}RuleFunction<TFact>
Type for a simple rule function:
import type { RuleFunction } from 'use-logic-rules'
const myRule: RuleFunction<MyFact> = (fact) => fact.count > 0ConditionalRule<TFact>
Type for conditional rules with when/then structure:
import type { ConditionalRule } from 'use-logic-rules'
const conditionalRule: ConditionalRule<MyFact> = {
when: (fact) => fact.count > 0,
then: {
isPositive: () => true,
isLarge: (fact) => fact.count > 100
}
}Rule<TFact>
Union type of RuleFunction and ConditionalRule:
import type { Rule } from 'use-logic-rules'
const rule: Rule<MyFact> = (fact) => fact.count > 0
// or
const rule: Rule<MyFact> = {
when: (fact) => fact.count > 0,
then: { isPositive: () => true }
}InferRuleValues<TRules>
Utility type to extract the flattened boolean state type from a rules object:
import type { InferRuleValues } from 'use-logic-rules'
const rules = {
countIsEven: (fact: MyFact) => fact.count % 2 === 0,
conditional: {
when: (fact: MyFact) => fact.count > 0,
then: {
isPositive: () => true,
}
}
} as const
type MyState = InferRuleValues<typeof rules>
// Result: { countIsEven: boolean; isPositive: boolean }EnhancedSignal<T>
Signal type with an enhanced set method. Returned by the hook as $fact and $ruleValues:
import type { EnhancedSignal } from 'use-logic-rules'
const { $fact } = useLogicRules({ initialFact, rules })
// Can set directly or with a function
$fact.set({ count: 5, name: 'test' })
$fact.set(prev => { prev.count++ })UseLogicRulesOptions
Options type for the hook:
import type { UseLogicRulesOptions } from 'use-logic-rules'
const options: UseLogicRulesOptions = {
debug: true, // Enable debug logging
useReducer: true, // Enable reducer mode
delay: 100, // Delay for side effects in ms
}
const { state } = useLogicRules({ initialFact, rules }, options)License
MIT
