MeoNode UI
  • Getting Started
    • Overview
    • Installation
    • Usage
    • Styling
    • Theming
    • Rules & Patterns
    • Framework Integration
    • FAQ
    • Release Notes
  • Components

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:

  1. Install: npm install @meonode/ui
  2. Import: import { Column, H1, Button } from '@meonode/ui'
  3. Build: Start using function calls instead of JSX
  4. Type Safety: Leverage TypeScript for autocomplete and error detection
  5. Theme: Explore the integrated theming system
  6. 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:

  1. Use Node() for all JSX components - This ensures proper integration
  2. Pass props normally - No special syntax or conversion needed
  3. Mix freely - You can alternate between MeoNode and JSX components seamlessly
  4. 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 components
  • createNode() - When you'll use a JSX component multiple times
  • createChildrenFirstNode() - 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

FunctionUsage PatternReusableDefault PropsBest For
Node()Node(Component, props)❌ No❌ NoOne-off usage
createNode()Factory({ prop, children })✅ Yes✅ YesProps-first components (inputs, pickers)
createChildrenFirstNode()Factory('content', { prop })✅ Yes✅ YesContent-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:

  1. Always use normal function declarations for page components
  2. Always call .render() on the returned MeoNode
  3. Avoid Component() factory for top-level page components
  4. 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:

  1. Use .tsx file extensions for all component files
  2. 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 props object
  • 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 Component HOC 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