MeoNode UI LogoMeoNode UI
  • Getting Started
    • Overview
    • Why Without JSX?
    • Installation
    • Usage
    • Styling
    • Theming
    • Portal System
    • Rules & Patterns
    • Framework Integration
    • FAQ
    • Release Notes
  • MUI Integration
  • Components
  • Hooks

Rules & Patterns — MeoNode UI

A scannable reference for the rules, conventions, and patterns that come up most when building with @meonode/ui. Most items link to a deeper guide for the full story.


Rules

These cause runtime or type errors if you ignore them.

Components passed to Node() must return a ReactElement

For MeoNode functions, that means calling .render() at the top level:

const Bad = () => Div({ children: 'Hi' })           // returns Node instance
const Good = () => Div({ children: 'Hi' }).render() // returns ReactElement

Node(Bad)   // ❌ React error
Node(Good)  // ✅

JSX components already return ReactElements, so they work as-is.

Don't wrap built-in MeoNode exports in Node()

Div, Button, ThemeProvider, etc. are already factories — call them directly.

ThemeProvider({ theme: myTheme })       // ✅
Div({ children: 'Hi' })                  // ✅

Node(ThemeProvider, { theme: myTheme })  // ❌ double-wrapping
Node(Div, { children: 'Hi' })            // ❌

Don't call hook-using components conditionally

Direct calls like cond && MyHookComponent() change the hook order between renders and crash with "Rendered fewer hooks than expected". Wrap with Node() instead:

cond && Node(MyHookComponent, { ... })  // ✅

Full pattern (with inline-function and Component HOC alternatives) → conditional hooks FAQ.


Conventions

These are stylistic — code works either way, but matching the convention makes diffs cleaner.

Define your own components with node functions, not JSX

MeoNode integrates fine with JSX (Node(JSXComponent, { … })), but the idiomatic style for your own components is the function-call API:

const Card = ({ title }: { title: string }) =>
  Column({
    padding: 16,
    children: [H2(title), Text('…')],
  }).render()

Only the top-level node calls .render()

Children inside children: [...] are rendered automatically. Calling .render() on every child is valid but noisy:

// ❌ noisy
Column({ children: [H1('Title').render(), Text('Sub').render()] }).render()

// ✅ clean
Column({ children: [H1('Title'), Text('Sub')] }).render()

The exception: pass .render()-ed children when a custom prop expects a raw ReactElement (e.g. an icon prop on a third-party Button):

Button('Settings', { icon: Node(SettingsIcon).render() })

Styling Patterns

Div({
  // CSS props go straight on root
  backgroundColor: 'red',
  padding: 20,
  borderRadius: 8,

  // DOM attributes / handlers / non-CSS custom props also at root
  onClick: handleClick,
  id: 'my-div',
  'aria-label': 'Label',
})

When a custom prop name collides with a CSS property but is meant as logic (e.g. height for a chart's render dimensions), nest it under props so the styling engine ignores it:

Node(Chart, {
  props: { height: 500 }, // forwarded as logic, not styled
  padding: 20,            // styled (container)
})

Full styling reference → Styling guide.


Performance Patterns

Pass a dependency array as the second argument to memoize a node.

Div({ children: 'static' }, [])             // renders once, never updates
Div({ children: `Count: ${c}` }, [c])       // updates only when c changes

The same [deps] argument works on Component-wrapped functions: MyHocComponent(props, deps).


Component Factories

Pick by usage shape:

FactoryUse caseExample
Node()One-off usage, especially for JSX componentsNode(TextField, { … })
createNode()Reusable factory, props-firstconst Field = createNode(Input)
createChildrenFirstNode()Reusable factory, children-firstconst Btn = createChildrenFirstNode(Button)
Component()Encapsulated logic + UI as a real React componentconst Card = Component(props => …)

Comparison & deeper trade-offs → Node vs createNode vs createChildrenFirstNode FAQ.


Hot Module Replacement

For Vite, sub-components hot-reload reliably when you:

  1. Use .tsx file extensions
  2. Wrap them with Node() in the parent
  3. Return a ReactElement (call .render())

Next.js HMR detects MeoNode components without all three, but following the pattern is still the safe default.

Full discussion → HMR sub-components FAQ.


Common Errors

SymptomCauseFix
"Rendered fewer hooks than expected"Hook-using component called directly inside a conditionalWrap with Node()
Style prop ignored on a custom componentComponent doesn't spread ...props to its rootSpread ...props (excluding children)
Node(MyComponent) throws / blank renderComponent returned a Node instance instead of a ReactElementAdd .render() to the top-level node
Custom prop appears in DOM / breaks layoutProp name matches a CSS property and got styledMove it under props: { yourProp }

Next Steps

On this page