Frequently Asked Questions — MeoNode UI
A collection of common questions and answers to help you get started quickly with MeoNode UI.
19 questions found
Quick Navigation
Common Questions
MeoNode UI is a powerful and opinionated React UI library designed to streamline the development of modern, scalable, and highly maintainable user interfaces. It champions a component-first architecture and type-safe design, offering a unique approach to building UIs that prioritizes structure, predictability, and developer experience.
At its heart, MeoNode UI allows you to define and compose your UI using declarative function calls and plain JavaScript objects, moving away from traditional JSX-heavy component definitions towards a more structured and functional paradigm.
- No Build Steps Required: JSX requires build steps, transforms, and special tooling. MeoNode UI works with pure JavaScript/TypeScript
- Type-Safe by Design: Comprehensive TypeScript integration with full type inference and autocomplete
- Better Developer Experience: Immediate feedback, fewer errors, and faster development cycles
- Structured Architecture: Component-first approach promotes better organization
- Integrated Theming: Robust theme system built directly into styling capabilities
- Performance Optimized: Efficient style generation and composition
Follow these simple steps:
- Install:
npm install @meonode/ui - Import:
import { Column, H1, Button } from '@meonode/ui' - Build: Start using function calls instead of JSX
- Type Safety: Leverage TypeScript for autocomplete and error detection
- Theme: Explore the integrated theming system
- Learn: Check the documentation for advanced patterns and best practices
Install the package using npm or yarn:
npm install @meonode/ui # or yarn add @meonode/ui
The package includes React and React-DOM as peer dependencies. No additional setup or build configuration is required.
MeoNode UI seamlessly integrates with existing JSX components and libraries using the Node() function:
Basic JSX Component Integration:
// Existing JSX component const MyJSXComponent = (props) => <div className="card">{props.children}</div> // Use with MeoNode UI const MyApp = Column({ children: [ H1('Welcome to MeoNode'), Node(MyJSXComponent, { children: 'This JSX component works perfectly!' }), Button('MeoNode Button', { backgroundColor: 'blue' }) ] })
Third-Party Library Integration:
// Using a third-party JSX library (e.g., Material-UI, Ant Design) import { DatePicker } from 'antd' import { TextField } from '@mui/material' const FormComponent = Column({ gap: 16, children: [ Node(TextField, { label: 'Name', variant: 'outlined', fullWidth: true, }), Node(DatePicker, { placeholder: 'Select date', style: { width: '100%' }, }), Button('Submit', { backgroundColor: 'green' }), ], })
Complex JSX Integration:
// JSX component with hooks and complex logic const ComplexJSXChart = ({ data, onSelect }) => { const [selected, setSelected] = useState(null) return ( <div className="chart-container"> <SomeChartLibrary data={data} onSelect={(item) => { setSelected(item) onSelect?.(item) }} /> {selected && <div>Selected: {selected.name}</div>} </div> ) } // Use in MeoNode const Dashboard = Column({ children: [ H1('Analytics Dashboard'), Node(ComplexJSXChart, { data: chartData, onSelect: (item) => console.log('Chart item selected:', item) }) ] })
Key Points:
- No conversion needed - JSX components work as-is
- Props pass through normally - All props work exactly as expected
- Event handlers work - onClick, onChange, etc. all function normally
- Hooks and state work - JSX components retain all React functionality
- Styling is preserved - CSS classes, inline styles, and component styling remain intact
Best Practices:
- Use Node() for all JSX components - This ensures proper integration
- Pass props normally - No special syntax or conversion needed
- Mix freely - You can alternate between MeoNode and JSX components seamlessly
- Gradual adoption - Start with MeoNode for new components while keeping existing JSX
Advanced Integration with Factory Functions:
MeoNode provides factory functions to create reusable node wrappers that follow MeoNode conventions:
import { createNode, createChildrenFirstNode } from '@meonode/ui' import { TextField, Button as MuiButton } from '@mui/material' import { DatePicker } from 'antd' // Create node factories with optional initial props const StyledTextField = createNode(TextField, { variant: 'outlined', fullWidth: true, }) const PrimaryButton = createNode(MuiButton, { variant: 'contained', color: 'primary', }) const MyDatePicker = createNode(DatePicker) // Create children-first nodes (for typography, buttons, or text-focused components) const StyledButton = createChildrenFirstNode(MuiButton, { variant: 'contained', size: 'large', }) const CustomHeading = createChildrenFirstNode(SomeTypographyComponent, { variant: 'h2', color: 'primary', }) // Usage follows MeoNode patterns const LoginForm = Column({ gap: 16, children: [ StyledTextField({ label: 'Username', type: 'text', }), StyledTextField({ label: 'Password', type: 'password', }), MyDatePicker({ placeholder: 'Birth Date', }), // Children-first pattern for text-focused components StyledButton('Login', { onClick: handleLogin, }), CustomHeading('Welcome Back!', { textAlign: 'center', }), ], })
Factory Function Benefits:
- Consistent API - All components follow MeoNode conventions
- Reusable configurations - Set default props once, use everywhere
- Better IntelliSense - TypeScript autocomplete works perfectly
- Cleaner code - No need to repeat
Node()calls - Performance - Factory functions are created once, reused many times
When to Use Each Pattern:
Node()- Quick one-off usage of JSX componentscreateNode()- When you'll use a JSX component multiple timescreateChildrenFirstNode()- For components that primarily wrap content (like cards, containers)
This makes MeoNode UI perfect for gradual adoption - you can start using it in new parts of your app while keeping all existing JSX components working perfectly.
These three functions serve different purposes for integrating JSX/React components into MeoNode UI:
Node()
Purpose: Quick one-off usage of JSX/React components
When to use: When you need to render an existing component immediately without creating a reusable wrapper.
import { Column, Node } from '@meonode/ui' import { TextField } from '@mui/material' const MyForm = Column({ children: [Node(TextField, { label: 'Username', variant: 'outlined' }), Node(TextField, { label: 'Password', type: 'password' })], })
createNode()
Purpose: Creates a reusable node factory with props-first pattern
Pattern: Single props argument where children must be passed as a prop
When to use: When you'll use a component multiple times and want to set default props
import { createNode } from '@meonode/ui' import { TextField } from '@mui/material' // Create reusable factory with default props const StyledTextField = createNode(TextField, { variant: 'outlined', fullWidth: true, }) // Usage - single props object const MyForm = Column({ children: [StyledTextField({ label: 'Username' }), StyledTextField({ label: 'Password', type: 'password' })], })
createChildrenFirstNode()
Purpose: Creates a reusable node factory with children-first pattern
Pattern: Two arguments - children first, then props object
When to use: For text-focused components like buttons, headings, or typography where content is the primary focus
import { createChildrenFirstNode } from '@meonode/ui' import { Button as MuiButton } from '@mui/material' // Create reusable factory with default props const PrimaryButton = createChildrenFirstNode(MuiButton, { variant: 'contained', color: 'primary', size: 'large', }) // Usage - children first, props second const MyForm = Column({ children: [PrimaryButton('Login', { onClick: handleLogin }), PrimaryButton('Sign Up', { onClick: handleSignup })], })
Quick Comparison
| Function | Usage Pattern | Reusable | Default Props | Best For |
|---|---|---|---|---|
Node() | Node(Component, props) | ❌ No | ❌ No | One-off usage |
createNode() | Factory({ prop, children }) | ✅ Yes | ✅ Yes | Props-first components (inputs, pickers) |
createChildrenFirstNode() | Factory('content', { prop }) | ✅ Yes | ✅ Yes | Content-first components (buttons, text) |
Key Takeaway
- Node(): Direct usage, no abstraction
- createNode(): Reusable factory, 1 argument (props with children inside)
- createChildrenFirstNode(): Reusable factory, 2 arguments (children, then props)
Next.js has strict TypeScript requirements for Page components that can cause build errors when using HOC (Higher-Order Components) patterns.
❌ Avoid Using HOC Component for Pages:
// DON'T DO THIS - Will cause TypeScript build errors const HomePage = Component(() => { return Column({ children: [Text('Hello World')], }) }) export default HomePage // ❌ TypeScript error
✅ Use Normal Functions for Pages:
// DO THIS - Works correctly with Next.js export default function HomePage() { return Column({ children: [Text('Hello World')], }).render() // ✅ Don't forget .render() on top level page }
Why This Happens:
- Next.js enforces strict typing for page component props and return types
- HOC components don't match Next.js's expected page component signature
- Page components must be standard React function components or class components
- Type conflicts arise between MeoNode's Component factory and Next.js page requirements
Key Rules for Next.js Pages:
- Always use normal function declarations for page components
- Always call .render() on the returned MeoNode
- Avoid Component() factory for top-level page components
- Use Component() factory for reusable sub-components only
Sub-components Can Still Use HOC:
// Sub-component using HOC - This is fine const MyButton = Component<{ text: string }>(props => { return Button(props.text, { backgroundColor: 'blue', color: 'white', }) }) // Page component using normal function export default function HomePage() { return Column({ children: [Node(MyButton, { text: 'Click me!' })], }).render() // ✅ Always call .render() on top level page }
Hot Module Replacement (HMR) requires proper setup for MeoNode UI sub-components:
For Vite Projects:
- Use .tsx file extensions for all component files
- Wrap components with Node() in the parent component
// AnotherComponent.tsx import { Column, Text } from '@meonode/ui' export function AnotherComponent() { return Column({ children: [Text('Hello from sub-component!')], }).render() // ✅ Call .render() to return React Element } // page.tsx import { Column, Node } from '@meonode/ui' import { AnotherComponent } from './AnotherComponent' export default function Page() { return Column({ children: [ Node(AnotherComponent), // ✅ Function that return React Element is wrapped with Node() ], }).render() }
For Next.js Projects:
Next.js is more intelligent with HMR detection:
- File extensions can be .ts or .tsx
- Node() wrapping is still recommended but not always required
- Next.js automatically handles most HMR scenarios
Why This Works:
- Node() wrapper ensures React can properly track component boundaries
- .tsx extensions help Vite's HMR system recognize React components
- Proper module boundaries allow hot reloading to work correctly
Without these patterns, changes to sub-components may require full page refreshes instead of hot updates.
Node functions are the building blocks of MeoNode UI. They come in two types:
Text-first nodes (H1, Button, Text, Span): First argument is text content, second is props
H1('Welcome!', { fontSize: '2rem', color: '#333' }) Button('Click Me', { backgroundColor: 'blue', onClick: handleClick })
Container nodes (Column, Row, Div): Children passed via children property
Column({ padding: 20, children: [H1('Title'), Text('Content')], })
Styling is done through props using a CSS-in-JS approach:
- Raw numbers are interpreted as pixels (20 becomes 20px)
- CSS properties can be passed directly as props
- Theme integration allows you to use design tokens
- Type safety provides autocomplete and error checking
Button('Submit', { padding: '10px 20px', backgroundColor: 'theme.colors.primary', borderRadius: 5, boxShadow: 'theme.shadow.md', })
How do I pass props like height to custom components?
When creating custom components in MeoNode UI, you need to understand how props are processed:
The Rule
- CSS-like prop names (height, width, padding, margin, etc.) used for non-styling purposes → must go in the
propsobject - Non-CSS prop names (data, title, onClick, etc.) → can be passed at root level
Why?
MeoNode UI processes CSS-like prop names as styles. If you have a custom component that needs height for logic (not
styling), passing it directly will be intercepted by the style processor.
Example
import { Component, Div } from '@meonode/ui' type ChartProps = { height: number // For chart logic, not styling data: number[] // Not a CSS prop } const Chart = Component<ChartProps>(({ height, data }) => Div({ children: `Chart with ${data.length} points at ${height}px height`, }), ) // Usage with Node Node(Chart, { backgroundColor: 'white', // Styling prop padding: '20px', // Styling prop data: [1, 2, 3, 4], // Root level - not CSS-like props: { // CSS-like custom props here height: 500, // Goes to component logic, not styling }, })
When to use props
Only use the props object when your custom component prop has the same name as a CSS property but serves a different
purpose. Otherwise, pass props directly at root level.
Use the Component factory to create reusable React components:
interface PrimaryButtonProps { onClick: () => void; } const PrimaryButton = Component<PrimaryButtonProps>((props) => { return Button(props.children, { padding: '12px 24px', backgroundColor: 'darkgreen', color: 'white', borderRadius: 8, ...props // Spread other props }); });
Always spread ...props to ensure additional properties are correctly applied.
The "Rendered fewer hooks than expected" (or similar) error comes from violating the Rules of Hooks. React requires that hooks like useState and useEffect are called in the same order on every render. If a component that uses hooks is only sometimes called (e.g. inside a conditional), the order of hook calls changes, and React throws this error.
Example: DetailComponent
In this guide we use DetailComponent as an example.
It returns a Node instance and uses hooks internally:
const DetailComponent = ({ info }: { info: string }) => { useEffect(() => { console.log('Mounted:', info) return () => console.log('Unmounted:', info) }, [info]) return Row({ alignItems: 'center', gap: 10, padding: 4, border: '2px solid theme.accent', borderRadius: 6, backgroundColor: 'theme.warning', color: 'theme.danger', children: [P(info), TextField({ sx: { background: 'theme.primary' } })], }).render() }
Because it calls useEffect, this component must always be rendered consistently.
Here are the safe and unsafe ways to do it:
✅ Correct Pattern: Wrap with Node()
The most idiomatic MeoNode pattern is to wrap hook-using Node components with the Node() HOC.
This makes them safe to conditionally render, because React now sees a proper component boundary.
isShowMore && Node(DetailComponent, { info: 'Safe: Node() wrapper ensures hook order is preserved.', })
✅ Correct Pattern: Inline Function Wrapper
Alternatively, you can wrap the call in an arrow function.
This delays execution until render time and avoids direct conditional hook calls.
isShowMore && (() => DetailComponent({ info: 'Safe: inline function wrapper.', }))
This also works if the component explicitly calls .render() internally (e.g. ReturnRenderedDetailComponent).
✅ Correct Pattern: Component HOC
For components wrapped in the Component HOC, React handles hooks as usual.
This is another safe way to render them conditionally.
const WrappedDetailComponent = Component(DetailComponent) isShowMore && WrappedDetailComponent({ info: 'Safe: Component HOC ensures hook order + theme context.', })
❌ Incorrect Pattern: Direct Conditional Call
Directly calling a hook-using component inside a conditional breaks the Rules of Hooks.
React sees a different hook order on renders and throws.
// ❌ Will throw "Rendered fewer hooks than expected" isShowMore && DetailComponent({ info: 'This violates hook rules.', })
Summary
Node()is the recommended MeoNode-safe wrapper for conditional rendering.- Inline functions and the
ComponentHOC are also safe, but use them when you need ReactNode compatibility or full theme propagation. - Never call a hook-using component directly inside a conditional — that’s what triggers the error.
Standard React event handlers work exactly as expected. Attach them as properties in the props object:
const handleClick = () => { console.log('Button clicked!') } const MyButton = Button('Click Me', { onClick: handleClick, onMouseEnter: () => console.log('Hover!'), backgroundColor: 'purple', })
Conditional Rendering:
const isLoggedIn = true const AuthStatus = Column({ children: isLoggedIn ? Text('Welcome back!', { color: 'green' }) : Button('Login', { backgroundColor: 'blue' }), })
List Rendering:
const items = [{ id: 1, text: 'Item 1' }] const ItemList = Column({ children: items.map(item => Text(item.text, { key: item.id, padding: 8, backgroundColor: '#e9e9e9', }), ), })
MeoNode UI provides several layout components:
- Column: Vertical layout (flexDirection: column)
- Row: Horizontal layout (flexDirection: row)
- Div: Generic container
- Section, Header, Footer, Main, Nav, Form: Semantic HTML containers
All layout components support flexbox properties and theme-aware styling.
Most standard HTML tags are also prebuilt as components, so you can use them directly in your UI code.
MeoNode UI integrates a robust theming system directly into styling:
- Define design tokens once and use them across components
- Theme-aware properties like
theme.colors.primary,theme.spacing.md - Global style changes made easy through centralized theme configuration
- Consistent visual language across your entire application
The theme system ensures design consistency with minimal effort.
Yes! MeoNode UI is designed exclusively for React and ensures seamless integration with:
- Next.js - Full support with SSR/SSG
- Vite - Optimized development experience
- Create React App - Works out of the box
- Other React tooling - Compatible with most React-based setups
No special configuration or build steps are required beyond standard React setup.
MeoNode UI is ideal for:
- Teams building large-scale applications where maintainability and consistency are paramount
- TypeScript enthusiasts who want to leverage type safety for UI development
- Developers looking for alternatives to JSX-heavy component declarations
- Projects requiring robust theming solutions out-of-the-box
- Teams that value clear component definitions and type contracts for better collaboration