This tool uses a hybrid data flow model combining the best of component-based and reactive paradigms:
- Props for Parent/Child: Explicit, local data passing through component tree
- Reactive Variables for Cross-Cutting: Shared state accessible from anywhere
- Events for Communication: Components emit events, others can listen
This gives you the clarity of React's unidirectional data flow with the convenience of global reactive state.
Rise's state system uses persistent reactive state that survives across logic flow executions:
// ❌ Traditional (ephemeral - state dies after function)
function handleClick() {
let toggle = true; // Dies when function ends
showMenu();
}
// ✅ Rise (persistent - state lives throughout page session)
Page State: { toggle: false }
Logic Flow A: [Button Click] → [Set toggle = true]
↓
State persists globally
↓
Logic Flow B: [Button Click Again] → [Read toggle] → "It's true, toggle it off"Benefits:
- Multiple independent logic flows can coordinate through shared state
- Components automatically react to state changes
- State survives across logic flow executions
- No need for complex flow-to-flow connections
Rise provides three levels of state scope:
- User authentication
- Theme preferences
- Shopping cart
- Global settings
- Form data
- UI toggles (dropdowns, modals)
- Filters, search terms
- Validation errors
- Input focus
- Hover state
- Animation progress
See LOGIC_SYSTEM.md for complete implementation details.
Page State:
- Page Mount: State initialized with defaults from manifest
- Logic Flows Execute: Read and write state values
- Components React: Auto-update when state changes
- Logic Flows Re-execute: Can read updated state
- Page Unmount: State destroyed (unless app-level)
State Generation:
Generated Zustand store from manifest:
// From manifest state definition
{
"state": {
"authMode": { "type": "string", "default": "signup" },
"email": { "type": "string", "default": "" }
}
}
// Generated code
import { create } from 'zustand';
export const usePageState = create((set, get) => ({
authMode: 'signup',
email: '',
setAuthMode: (mode) => set({ authMode: mode }),
setEmail: (email) => set({ email }),
toggleAuthMode: () => set({
authMode: get().authMode === 'login' ? 'signup' : 'login'
}),
reset: () => set({ authMode: 'signup', email: '' })
}));When to use: Passing data from parent to direct children
Parent Component
├─ prop: userData
↓
Child Component
└─ receives: userData
In Manifest:
{
"id": "userCard",
"type": "UserCard",
"props": {
"user": {
"type": "binding",
"source": "parent.userData"
}
}
}Generated Code:
<UserCard user={userData} />Visual Editor: Drag wire from parent's userData state to child's user prop input
When to use: Child needs to notify parent of actions
Child Component
├─ event: onUserClick
↑
Parent Component
└─ handler: handleUserClick
In Manifest:
{
"id": "userCard",
"events": {
"onUserClick": {
"type": "emit"
}
}
},
{
"id": "userList",
"handlers": {
"userCard.onUserClick": {
"type": "function",
"action": "handleUserClick"
}
}
}Generated Code:
// UserCard.jsx
export function UserCard({ onUserClick }) {
return <button onClick={() => onUserClick(user)}>Click</button>;
}
// UserList.jsx
export function UserList() {
const handleUserClick = (user) => {
console.log('User clicked:', user);
};
return <UserCard onUserClick={handleUserClick} />;
}Visual Editor: Draw connection from child's onUserClick output to parent's handler
When to use: Data needed across multiple unrelated components
Component A
↓ updates
Global.currentUser
↓ reads
Component B (anywhere in tree)
In Variables Config (.lowcode/variables.json):
{
"currentUser": {
"type": "object",
"reactive": true,
"default": null,
"schema": {
"id": "string",
"name": "string",
"email": "string"
}
},
"theme": {
"type": "string",
"reactive": true,
"default": "light",
"options": ["light", "dark"]
}
}In Component Manifest:
{
"id": "userProfile",
"props": {
"userName": {
"type": "expression",
"value": "global.currentUser.name"
}
}
}Generated Code:
import { useGlobalState } from '../runtime/globalState';
export function UserProfile() {
const currentUser = useGlobalState('currentUser');
return <h1>{currentUser.name}</h1>;
}Visual Editor:
- See reactive variables in a global panel
- Reference them in any property field
- Shows which components use which variables
Components can expose internal state to become available for connections:
{
"id": "searchBox",
"state": {
"query": {
"type": "string",
"default": "",
"exposed": false // ← Only this component can access
}
}
}{
"id": "searchBox",
"state": {
"query": {
"type": "string",
"default": "",
"exposed": true // ← Other components can read/connect
}
}
}When exposed, other components can bind to it:
{
"id": "searchResults",
"props": {
"searchTerm": {
"type": "binding",
"source": "searchBox.state.query"
}
}
}The visual editor shows connections:
┌─────────────────┐
│ UserList │
│ │
│ state: │
│ • users [] │◄─── (binds to) ─────┐
│ │ │
│ events: │ │
│ • onUserClick │─────────┐ │
└─────────────────┘ │ │
│ │
┌───────────────────▼───────────┼──────┐
│ │ │
│ ┌─────────────────┐ ┌──────▼────┐ │
│ │ UserCard │ │ global │ │
│ │ │ │ │ │
│ │ props: │ │ • users │ │
│ │ • user │ │ • theme │ │
│ │ • onClick │ └───────────┘ │
│ └─────────────────┘ │
│ │
└──────────────────────────────────────┘
Wire Colors:
- 🔵 Blue: Props (data down)
- 🟢 Green: Events (actions up)
- 🟣 Purple: Reactive variables (global)
- 🟡 Yellow: Exposed state (peer-to-peer)
Components can define computed values that update reactively:
{
"id": "userStats",
"computed": {
"fullName": {
"type": "expression",
"value": "global.currentUser.firstName + ' ' + global.currentUser.lastName"
},
"isAdmin": {
"type": "expression",
"value": "global.currentUser.role === 'admin'"
}
}
}Generated Code:
import { useGlobalState } from '../runtime/globalState';
import { useMemo } from 'react';
export function UserStats() {
const currentUser = useGlobalState('currentUser');
const fullName = useMemo(
() => currentUser.firstName + ' ' + currentUser.lastName,
[currentUser.firstName, currentUser.lastName]
);
const isAdmin = useMemo(
() => currentUser.role === 'admin',
[currentUser.role]
);
return <div>{fullName} {isAdmin && '(Admin)'}</div>;
}For complex logic, create script nodes that sit between components:
{
"id": "validateUser",
"type": "ScriptNode",
"inputs": {
"userData": { "type": "object" }
},
"outputs": {
"isValid": { "type": "boolean" },
"errors": { "type": "array" }
},
"script": "./scripts/validateUser.js"
}In Visual Editor: Appears as node that can be wired to components
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ UserForm │ │ validateUser │ │ SubmitBtn │
│ │ │ │ │ │
│ • userData │──────▶│ • userData │ │ • disabled │
│ │ │ │ │ │
│ │ │ • isValid │──────▶│ │
│ │ │ • errors │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
Script File (scripts/validateUser.js):
export function validateUser(inputs) {
const { userData } = inputs;
const errors = [];
if (!userData.email.includes('@')) {
errors.push('Invalid email');
}
if (userData.password.length < 8) {
errors.push('Password too short');
}
return {
isValid: errors.length === 0,
errors
};
}
export const metadata = {
inputs: {
userData: { type: 'object', required: true }
},
outputs: {
isValid: { type: 'boolean' },
errors: { type: 'array' }
}
};The step debugger shows data flow in real-time:
Debug Mode UI:
┌─────────────────────────────────────────────────────────┐
│ Step Debugger │
├─────────────────────────────────────────────────────────┤
│ │
│ Current Event: onClick (UserCard #user-123) │
│ │
│ Data Flow: │
│ 1. ✓ Button clicked │
│ 2. → Event emitted: onUserClick({ id: 123 }) │
│ 3. → Parent handler: handleUserClick(user) │
│ 4. ⏸ About to execute: updateGlobal('currentUser') │
│ │
│ Component State: │
│ • UserCard.state.isHovered: true │
│ • UserList.state.selectedId: null → 123 (pending) │
│ │
│ Reactive Variables: │
│ • global.currentUser: { id: 100 } → { id: 123 } │
│ ↳ Will trigger re-render in: UserProfile, Header │
│ │
│ [Continue] [Step Over] [Step Into] │
└─────────────────────────────────────────────────────────┘
- Use props for data that flows down the component tree
- Use events to notify parents of user actions
- Use reactive variables for truly global state (user, theme, app config)
- Expose state when sibling components need to coordinate
- Use script nodes for reusable logic that multiple components need
- Pass data through many prop layers (use reactive variables instead)
- Put component-specific state in global variables
- Create too many reactive variables (keep them minimal)
- Forget to expose state that siblings need
- Put complex logic directly in component properties (use script nodes)
Uses Zustand for global state management:
// runtime/globalState.ts
import create from 'zustand';
interface GlobalState {
currentUser: User | null;
theme: 'light' | 'dark';
setCurrentUser: (user: User) => void;
setTheme: (theme: 'light' | 'dark') => void;
}
export const useGlobalState = create<GlobalState>((set) => ({
currentUser: null,
theme: 'light',
setCurrentUser: (user) => set({ currentUser: user }),
setTheme: (theme) => set({ theme })
}));Generated automatically from .lowcode/variables.json
Standard React patterns - no magic needed
Managed through React Context when needed for sibling access:
// Auto-generated when state is exposed
export const SearchContext = createContext();
export function SearchBox() {
const [query, setQuery] = useState('');
return (
<SearchContext.Provider value={{ query, setQuery }}>
{/* component content */}
</SearchContext.Provider>
);
}
// Other components can access
export function SearchResults() {
const { query } = useContext(SearchContext);
return <div>Results for: {query}</div>;
}See Also:
- Expression System - How to reference data in properties
- Debugger Design - How data flow is visualized during debug
- Component Schema - How data flow is represented in manifest