22 KiB
CODE-REFERENCE: Noodl Node Export Reference
Overview
This document provides a comprehensive reference for how each Noodl-specific node type transforms into generated React code. This is the definitive guide for implementing the code generator.
Table of Contents
- State Nodes
- Component Scope Nodes
- Event Nodes
- Logic Nodes
- Visual Container Nodes
- Data Manipulation Nodes
- Navigation Nodes
- Utility Nodes
State Nodes
Variable / String / Number / Boolean / Color
Purpose: Global reactive values that can be read/written from anywhere.
Noodl Pattern:
┌──────────────┐
│ Variable │
│ name: "foo" │──○ Value
│ value: 123 │
└──────────────┘
Generated Code:
// stores/variables.ts
import { createVariable } from '@nodegx/core';
export const fooVar = createVariable<number>('foo', 123);
// Usage in component
import { useVariable } from '@nodegx/core';
import { fooVar } from '../stores/variables';
function MyComponent() {
const [foo, setFoo] = useVariable(fooVar);
return <span>{foo}</span>;
}
// Usage in logic
import { getVariable, setVariable } from '@nodegx/core';
import { fooVar } from '../stores/variables';
function updateFoo(newValue: number) {
setVariable(fooVar, newValue);
}
Set Variable
Purpose: Updates a Variable's value.
Noodl Pattern:
┌───────────────────┐
│ Set Variable │
│ name: "foo" │
│─○ Value │
│─○ Do │──○ Done
└───────────────────┘
Generated Code:
// Inline in component
import { fooVar } from '../stores/variables';
<button onClick={() => fooVar.set(newValue)}>
Update
</button>
// Or via hook
const [, setFoo] = useVariable(fooVar);
<button onClick={() => setFoo(newValue)}>
Update
</button>
Object
Purpose: Read properties from a reactive object by ID.
Noodl Pattern:
┌────────────────┐
│ Object │
│ id: "user" │──○ name
│ │──○ email
│ │──○ avatar
└────────────────┘
Generated Code:
// stores/objects.ts
import { createObject } from '@nodegx/core';
export interface User {
name: string;
email: string;
avatar: string;
}
export const userObj = createObject<User>('user', {
name: '',
email: '',
avatar: ''
});
// Usage in component
import { useObject } from '@nodegx/core';
import { userObj } from '../stores/objects';
function UserProfile() {
const user = useObject(userObj);
return (
<div>
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Set Object Properties
Purpose: Updates properties on an Object.
Noodl Pattern:
┌──────────────────────┐
│ Set Object Props │
│ id: "user" │
│─○ name │
│─○ email │
│─○ Do │──○ Done
└──────────────────────┘
Generated Code:
import { userObj } from '../stores/objects';
// Single property
userObj.set('name', 'John Doe');
// Multiple properties
userObj.setProperties({
name: 'John Doe',
email: 'john@example.com'
});
// Replace entire object
userObj.setAll({
name: 'John Doe',
email: 'john@example.com',
avatar: '/avatars/john.jpg'
});
Array
Purpose: Read from a reactive array by ID.
Noodl Pattern:
┌──────────────┐
│ Array │
│ id: "items" │──○ Items
│ │──○ Count
└──────────────┘
Generated Code:
// stores/arrays.ts
import { createArray } from '@nodegx/core';
export interface Item {
id: string;
name: string;
price: number;
}
export const itemsArray = createArray<Item>('items', []);
// Usage in component
import { useArray } from '@nodegx/core';
import { itemsArray } from '../stores/arrays';
function ItemList() {
const items = useArray(itemsArray);
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// Get count
const count = itemsArray.length;
Static Array
Purpose: Constant array data (not reactive).
Noodl Pattern:
┌─────────────────────┐
│ Static Array │
│ items: [{...},...] │──○ Items
│ │──○ Count
└─────────────────────┘
Generated Code:
// stores/staticArrays.ts
export interface MenuItem {
label: string;
path: string;
icon: string;
}
export const menuItems: MenuItem[] = [
{ label: 'Home', path: '/', icon: 'home' },
{ label: 'Products', path: '/products', icon: 'box' },
{ label: 'Contact', path: '/contact', icon: 'mail' }
];
// Usage in component
import { menuItems } from '../stores/staticArrays';
function NavMenu() {
return (
<nav>
{menuItems.map(item => (
<NavLink key={item.path} to={item.path}>
<Icon name={item.icon} />
{item.label}
</NavLink>
))}
</nav>
);
}
States
Purpose: Simple state machine with named states and optional values per state.
Noodl Pattern:
┌──────────────────────┐
│ States │
│ states: [idle, │
│ loading, success, │
│ error] │
│ start: idle │
│─○ To Idle │
│─○ To Loading │
│─○ To Success │
│─○ To Error │──○ State
│ │──○ At Idle
│ │──○ At Loading
│ │──○ opacity (value)
└──────────────────────┘
Generated Code:
// stores/stateMachines.ts
import { createStateMachine } from '@nodegx/core';
export type LoadingState = 'idle' | 'loading' | 'success' | 'error';
export const loadingStateMachine = createStateMachine<LoadingState>({
states: ['idle', 'loading', 'success', 'error'],
initial: 'idle',
values: {
idle: { opacity: 1, message: '' },
loading: { opacity: 0.5, message: 'Loading...' },
success: { opacity: 1, message: 'Complete!' },
error: { opacity: 1, message: 'Error occurred' }
}
});
// Usage in component
import { useStateMachine, useStateValues } from '@nodegx/core';
import { loadingStateMachine } from '../stores/stateMachines';
function LoadingButton() {
const [state, goTo] = useStateMachine(loadingStateMachine);
const values = useStateValues(loadingStateMachine);
const handleClick = async () => {
goTo('loading');
try {
await doSomething();
goTo('success');
} catch {
goTo('error');
}
};
return (
<button
onClick={handleClick}
style={{ opacity: values.opacity }}
disabled={state === 'loading'}
>
{values.message || 'Submit'}
</button>
);
}
Component Scope Nodes
Component Object (Component State)
Purpose: State scoped to a component instance. Each instance has its own state.
Noodl Pattern:
┌────────────────────┐
│ Component Object │──○ count
│ (Component State) │──○ isOpen
└────────────────────┘
Runtime Behavior:
- Created when component mounts
- Destroyed when component unmounts
- Isolated per instance
Generated Code:
// components/Counter.tsx
import {
ComponentStoreProvider,
useComponentStore,
useSetComponentStore
} from '@nodegx/core';
interface CounterState {
count: number;
isOpen: boolean;
}
function CounterInner() {
const state = useComponentStore<CounterState>();
const { set } = useSetComponentStore<CounterState>();
return (
<div>
<span>{state.count}</span>
<button onClick={() => set('count', state.count + 1)}>+</button>
<button onClick={() => set('isOpen', !state.isOpen)}>Toggle</button>
</div>
);
}
export function Counter() {
return (
<ComponentStoreProvider<CounterState>
initialState={{ count: 0, isOpen: false }}
>
<CounterInner />
</ComponentStoreProvider>
);
}
Parent Component Object
Purpose: Read the Component Object of the visual parent component.
Noodl Pattern:
┌──────────────────────────┐
│ Parent Component Object │──○ selectedId
│ │──○ isExpanded
└──────────────────────────┘
Runtime Behavior:
- Reads from parent's Component Object
- Read-only (cannot set parent's state directly)
- Updates when parent's state changes
Generated Code:
// components/ListItem.tsx
import { useParentComponentStore } from '@nodegx/core';
interface ParentListState {
selectedId: string | null;
isExpanded: boolean;
}
function ListItem({ id, label }: { id: string; label: string }) {
const parentState = useParentComponentStore<ParentListState>();
const isSelected = parentState?.selectedId === id;
const showDetails = parentState?.isExpanded && isSelected;
return (
<div className={isSelected ? 'selected' : ''}>
<span>{label}</span>
{showDetails && <ItemDetails id={id} />}
</div>
);
}
Component Children
Purpose: Slot where child components are rendered.
Noodl Pattern:
Card Component:
┌──────────────────────┐
│ ┌────────────────┐ │
│ │ Header │ │
│ └────────────────┘ │
│ ┌────────────────┐ │
│ │ [Component │ │
│ │ Children] │ │
│ └────────────────┘ │
│ ┌────────────────┐ │
│ │ Footer │ │
│ └────────────────┘ │
└──────────────────────┘
Generated Code:
// components/Card.tsx
import styles from './Card.module.css';
interface CardProps {
children?: React.ReactNode;
title?: string;
}
export function Card({ children, title }: CardProps) {
return (
<div className={styles.card}>
<div className={styles.header}>
<h3>{title}</h3>
</div>
<div className={styles.content}>
{children}
</div>
<div className={styles.footer}>
<span>Card Footer</span>
</div>
</div>
);
}
// Usage
<Card title="My Card">
<p>This content goes in the children slot</p>
<Button>Click me</Button>
</Card>
For Each / Repeater
Purpose: Iterate over an array, rendering a template component for each item.
Noodl Pattern:
┌───────────────────┐
│ For Each │
│─○ Items │
│ template: "Card" │
└───────────────────┘
Generated Code:
// components/ItemList.tsx
import { useArray, RepeaterItemProvider } from '@nodegx/core';
import { itemsArray } from '../stores/arrays';
import { ItemCard } from './ItemCard';
export function ItemList() {
const items = useArray(itemsArray);
return (
<div className="item-list">
{items.map((item, index) => (
<RepeaterItemProvider
key={item.id}
item={item}
index={index}
itemId={`item_${item.id}`}
>
<ItemCard />
</RepeaterItemProvider>
))}
</div>
);
}
// ItemCard.tsx - the template component
import { useRepeaterItem, useRepeaterIndex } from '@nodegx/core';
interface Item {
id: string;
name: string;
price: number;
}
export function ItemCard() {
const item = useRepeaterItem<Item>();
const index = useRepeaterIndex();
return (
<div className="item-card">
<span className="index">#{index + 1}</span>
<h4>{item.name}</h4>
<span className="price">${item.price}</span>
</div>
);
}
Repeater Object (For Each Item)
Purpose: Access the current item inside a For Each template.
Noodl Pattern:
Inside For Each template:
┌──────────────────┐
│ Repeater Object │──○ id
│ │──○ name
│ │──○ price
└──────────────────┘
Generated Code:
import { useRepeaterItem } from '@nodegx/core';
function ProductCard() {
const product = useRepeaterItem<Product>();
return (
<div>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
);
}
Event Nodes
Send Event
Purpose: Broadcast an event with optional data.
Noodl Pattern:
┌─────────────────────┐
│ Send Event │
│ channel: "refresh" │
│ mode: "global" │
│─○ itemId │
│─○ Send │
└─────────────────────┘
Generated Code (Global):
// events/channels.ts
import { createEventChannel } from '@nodegx/core';
interface RefreshEvent {
itemId: string;
}
export const refreshEvent = createEventChannel<RefreshEvent>('refresh');
// Sending
import { refreshEvent } from '../events/channels';
function handleSend(itemId: string) {
refreshEvent.send({ itemId });
}
<button onClick={() => handleSend('123')}>Refresh</button>
Generated Code (Scoped - Parent/Children/Siblings):
import { useScopedEventSender } from '../events/ComponentEventContext';
function ChildComponent() {
const { sendToParent } = useScopedEventSender();
return (
<button onClick={() => sendToParent('itemSelected', { itemId: '123' })}>
Select
</button>
);
}
Receive Event
Purpose: Listen for events on a channel.
Noodl Pattern:
┌─────────────────────┐
│ Receive Event │
│ channel: "refresh" │──○ itemId
│ │──○ Received
└─────────────────────┘
Generated Code (Global):
// Using generated hook
import { useRefreshEvent } from '../events/hooks';
function DataPanel() {
useRefreshEvent((data) => {
console.log('Refresh requested for:', data.itemId);
fetchData(data.itemId);
});
return <div>...</div>;
}
// Or using generic hook
import { useEventChannel } from '../events/hooks';
import { refreshEvent } from '../events/channels';
function DataPanel() {
useEventChannel(refreshEvent, (data) => {
fetchData(data.itemId);
});
return <div>...</div>;
}
Generated Code (Scoped):
import { useScopedEvent } from '../events/ComponentEventContext';
function ParentComponent() {
useScopedEvent('itemSelected', (data: { itemId: string }) => {
setSelectedId(data.itemId);
});
return (
<ComponentEventProvider>
<ItemList />
</ComponentEventProvider>
);
}
Logic Nodes
Function Node
Purpose: Custom JavaScript code with inputs/outputs.
Noodl Pattern:
┌─────────────────────┐
│ Function │
│─○ value │
│─○ multiplier │──○ result
│─○ Run │──○ done
│ │
│ Script: │
│ const result = │
│ Inputs.value * │
│ Inputs.multiplier;│
│ Outputs.result = │
│ result; │
│ Outputs.done(); │
└─────────────────────┘
Generated Code:
// logic/mathFunctions.ts
import { createSignal } from '@nodegx/core';
export const onMultiplyDone = createSignal('onMultiplyDone');
export function multiply(value: number, multiplier: number): number {
const result = value * multiplier;
onMultiplyDone.send();
return result;
}
// Usage in component
import { multiply, onMultiplyDone } from '../logic/mathFunctions';
import { useSignal } from '@nodegx/core';
function Calculator() {
const [result, setResult] = useState(0);
useSignal(onMultiplyDone, () => {
console.log('Calculation complete!');
});
const handleCalculate = () => {
const newResult = multiply(value, multiplier);
setResult(newResult);
};
return <button onClick={handleCalculate}>Calculate</button>;
}
Expression
Purpose: Evaluate a JavaScript expression reactively.
Noodl Pattern:
┌────────────────────────────────────┐
│ Expression │
│─○ a │
│─○ b │──○ result
│ │──○ isTrue
│ expression: (a + b) * 2 │──○ isFalse
└────────────────────────────────────┘
Generated Code:
// Simple case - inline
const result = (a + b) * 2;
// With dependencies - useMemo
const result = useMemo(() => (a + b) * 2, [a, b]);
// Accessing Noodl globals
import { useVariable } from '@nodegx/core';
import { taxRateVar, subtotalVar } from '../stores/variables';
function TaxCalculator() {
const [taxRate] = useVariable(taxRateVar);
const [subtotal] = useVariable(subtotalVar);
const tax = useMemo(() => subtotal * taxRate, [subtotal, taxRate]);
return <span>Tax: ${tax.toFixed(2)}</span>;
}
Condition
Purpose: Route execution based on boolean value.
Generated Code:
// Visual conditional
{isLoggedIn ? <Dashboard /> : <LoginForm />}
// Logic conditional
if (isValid) {
onSuccess.send();
} else {
onFailure.send();
}
Switch
Purpose: Route based on value matching cases.
Generated Code:
// Component selection
const viewComponents = {
list: ListView,
grid: GridView,
table: TableView
};
const ViewComponent = viewComponents[viewMode] || ListView;
return <ViewComponent items={items} />;
// Action routing
switch (action) {
case 'save':
handleSave();
break;
case 'delete':
handleDelete();
break;
case 'cancel':
handleCancel();
break;
}
And / Or / Not
Purpose: Boolean logic operations.
Generated Code:
// And
const canSubmit = isValid && !isLoading && hasPermission;
// Or
const showWarning = hasErrors || isExpired;
// Not
const isDisabled = !isEnabled;
// Combined
const showContent = (isLoggedIn && hasAccess) || isAdmin;
Navigation Nodes
Page Router
Purpose: Define application routes.
Generated Code:
// App.tsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
export function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/products/:id" element={<ProductDetailPage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</BrowserRouter>
);
}
Navigate
Purpose: Programmatic navigation.
Generated Code:
import { useNavigate } from 'react-router-dom';
function NavButton({ to }: { to: string }) {
const navigate = useNavigate();
return (
<button onClick={() => navigate(to)}>
Go
</button>
);
}
// With parameters
const productId = '123';
navigate(`/products/${productId}`);
// With options
navigate('/login', { replace: true });
Page Inputs / Page Outputs
Purpose: Pass data to/from pages via route parameters.
Generated Code:
// Page Inputs (route parameters)
import { useParams, useSearchParams } from 'react-router-dom';
function ProductPage() {
// URL params (/products/:id)
const { id } = useParams<{ id: string }>();
// Query params (/products?category=electronics)
const [searchParams] = useSearchParams();
const category = searchParams.get('category');
return <div>Product {id} in {category}</div>;
}
// Page Outputs (navigate with state)
navigate('/checkout', { state: { cartItems } });
// Read in target page
import { useLocation } from 'react-router-dom';
const { state } = useLocation();
const cartItems = state?.cartItems;
Summary: Quick Reference Table
| Noodl Node | @nodegx/core Primitive | Generated Pattern |
|---|---|---|
| Variable | createVariable |
Store + useVariable hook |
| Object | createObject |
Store + useObject hook |
| Array | createArray |
Store + useArray hook |
| Static Array | N/A | Constant export |
| States | createStateMachine |
Store + useStateMachine hook |
| Component Object | ComponentStoreProvider |
Context provider wrapper |
| Parent Component Object | useParentComponentStore |
Context consumer hook |
| Component Children | N/A | {children} prop |
| For Each / Repeater | RepeaterItemProvider |
.map() with context |
| Repeater Object | useRepeaterItem |
Context consumer hook |
| Send Event (global) | events.emit |
Event channel |
| Send Event (scoped) | useScopedEventSender |
Context-based events |
| Receive Event | useEvent / hooks |
Event subscription |
| Function | N/A | Extracted function |
| Expression | N/A | Inline or useMemo |
| Condition | N/A | Ternary / if |
| Switch | N/A | switch / object lookup |
| And/Or/Not | N/A | && / ` |
| Navigate | useNavigate |
React Router |
| Page Router | <Routes> |
React Router |