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:
| Factory | Use case | Example |
|---|---|---|
Node() | One-off usage, especially for JSX components | Node(TextField, { … }) |
createNode() | Reusable factory, props-first | const Field = createNode(Input) |
createChildrenFirstNode() | Reusable factory, children-first | const Btn = createChildrenFirstNode(Button) |
Component() | Encapsulated logic + UI as a real React component | const 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:
- Use
.tsxfile extensions - Wrap them with
Node()in the parent - 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
| Symptom | Cause | Fix |
|---|---|---|
| "Rendered fewer hooks than expected" | Hook-using component called directly inside a conditional | Wrap with Node() |
| Style prop ignored on a custom component | Component doesn't spread ...props to its root | Spread ...props (excluding children) |
Node(MyComponent) throws / blank render | Component returned a Node instance instead of a ReactElement | Add .render() to the top-level node |
| Custom prop appears in DOM / breaks layout | Prop name matches a CSS property and got styled | Move it under props: { yourProp } |
Next Steps
- Framework Integration — Next.js, Vite, Remix
- FAQ — Common patterns and edge cases
- Release Notes — Changelog and upgrade guides
On this page
- Rules & Patterns — MeoNode UI