Skip to content

Components

A component is a function that returns JSX. Props in, JSX out. Lifecycle is managed by signals and effects.

Basic component

tsx
function Greeting(props: { name: string }) {
  return <h1>Hello, {props.name}!</h1>
}

<Greeting name="World" />

Props

Props are passed as a single object argument. Destructure for convenience:

tsx
function Button({ label, onClick }: { label: string; onClick: () => void }) {
  return <button onclick={onClick}>{label}</button>
}

Children

Children come through props.children:

tsx
function Card({ children }: { children: any }) {
  return <div class="card">{children}</div>
}

<Card>
  <h2>Title</h2>
  <p>Body</p>
</Card>

Conditional rendering

Use plain &&, ternaries, or early returns:

tsx
function Status({ ok }: { ok: boolean }) {
  return (
    <div>
      {ok && <p>All good</p>}
      {ok ? <span>✓</span> : <span>✗</span>}
    </div>
  )
}

Lists

Map an array. Always include key when items can reorder:

tsx
function TodoList({ items }: { items: { id: number; text: string }[] }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>{item.text}</li>
      ))}
    </ul>
  )
}

The key lets the reconciler match items across renders by identity instead of position. Toggling item 5 in a list of 100 re-renders only that one <li>.

Event handlers

Lowercase (DOM-native): onclick, oninput, onsubmit, onkeydown. Type as the corresponding DOM event:

tsx
function Search() {
  const [q, setQ] = use('')
  return (
    <input
      value={q}
      oninput={(e: Event) => setQ((e.target as HTMLInputElement).value)}
      onkeydown={(e: KeyboardEvent) => {
        if (e.key === 'Enter') console.log('search', q)
      }}
    />
  )
}

Why lowercase? Flexium passes attributes straight to the DOM — no synthetic event system. That's part of how it stays at 12 KB.

Refs

For imperative DOM access:

tsx
import { useRef } from 'flexium/core'

function FocusInput() {
  const ref = useRef<HTMLInputElement>(null)

  return (
    <div>
      <input ref={ref} />
      <button onclick={() => ref.current?.focus()}>Focus</button>
    </div>
  )
}

ref.current is set after mount and cleared on unmount.

Lifecycle

Use signals + effects for lifecycle. There's no useEffect(fn, []) ceremony:

tsx
function ChatRoom({ roomId }: { roomId: string }) {
  const [messages] = use<string[]>([])

  unsafeEffect(() => {
    const socket = connect(roomId)
    socket.on('message', m => setMessages(prev => [...prev, m]))

    // Cleanup — runs when component unmounts OR when roomId changes
    return () => socket.disconnect()
  })

  return <ul>{messages.map((m, i) => <li key={i}>{m}</li>)}</ul>
}

The effect re-runs when its captured signals change (here: roomId since it's a closed-over prop). The returned cleanup runs before the next execution AND on unmount.

Context

For deep prop passing without drilling:

tsx
import { Context, use } from 'flexium/core'

const ThemeCtx = Context.create<'light' | 'dark'>('light')

function App() {
  return (
    <ThemeCtx.Provider value="dark">
      <Header />
    </ThemeCtx.Provider>
  )
}

function Header() {
  const theme = use(ThemeCtx)
  return <header data-theme={theme}>...</header>
}

For app-wide singletons (settings, auth), prefer use(value, { key }) — see Signals.

Component shape stability

Flexium V8-optimizes component instances. Initialize all fields at construction (don't add properties later) for the fastest paths. The framework does this internally; your component code rarely needs to think about it.

Next

Renderingrender(), Suspense, ErrorBoundary, Portal.

Released under the MIT License.