A collection of common questions and answers to help you get started quickly with MeoNode UI.
17 questions found
Quick Navigation
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.
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:
// 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' }) ] })
// 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' }) ] })
// 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) }) ] })
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' }) ] })
Node()
callsNode()
- 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.
Next.js has strict TypeScript requirements for Page components that can cause build errors when using HOC (Higher-Order Components) patterns.
// DON'T DO THIS - Will cause TypeScript build errors const HomePage = Component(() => { return Column({ children: [ Text('Hello World'), ], }) }) export default HomePage // ❌ TypeScript error
// 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 }
// 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:
// 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() }
Next.js is more intelligent with HMR detection:
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:
Button('Submit', { padding: '10px 20px', backgroundColor: 'theme.colors.primary', borderRadius: 5, boxShadow: '0 2px 6px rgba(0, 0, 0, 0.08)' })
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.
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 #8c3f27', borderRadius: 6, backgroundColor: '#F9A825', color: '#C62828', children: [P(info), TextField({ sx: { background: '#000000' } })], }).render() }
Because it calls useEffect
, this component must always be rendered consistently.
Here are the safe and unsafe ways to do it:
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.', })
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
).
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.', })
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.', })
Node()
is the recommended MeoNode-safe wrapper for conditional rendering.Component
HOC are also safe, but use them when you need ReactNode compatibility or full theme propagation.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:
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:
theme.colors.primary
, 1rem
The theme system ensures design consistency with minimal effort.
Yes! MeoNode UI is designed exclusively for React and ensures seamless integration with:
No special configuration or build steps are required beyond standard React setup.
MeoNode UI is ideal for:
Follow these simple steps:
npm install @meonode/ui
import { Column, H1, Button } from '@meonode/ui'