JSX Syntax
PraxisJS uses a custom JSX runtime (@praxisjs/jsx). Files with JSX must use the .tsx extension.
Reactive vs static expressions
The key rule: arrow functions are reactive, plain expressions are static.
tsx
render() {
return (
<div>
{() => this.name} {/* reactive — updates on change */}
{this.name} {/* static — read once at render */}
{() => this.count * 2} {/* reactive expression */}
{() => this.active ? 'on' : 'off'} {/* reactive conditional */}
</div>
)
}Conditional rendering
tsx
render() {
return (
<div>
{/* Reactive conditional — re-evaluates when isOpen changes */}
{() => this.isOpen && <Modal />}
{/* Ternary */}
{() => this.loading ? <Spinner /> : <Content />}
</div>
)
}Lists
tsx
render() {
return (
<ul>
{() => this.items.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}Always provide a key prop when mapping lists. Keys must be stable and unique.
Event handlers
Event props use camelCase names (onClick, onInput, onKeyDown, etc.):
tsx
render() {
return (
<div>
<button onClick={() => this.count++}>Increment</button>
<input onInput={(e) => { this.value = (e.target as HTMLInputElement).value }} />
</div>
)
}Event handlers are plain arrow functions — they don't need to be reactive because they're callbacks, not DOM expressions.
CSS classes
Static class:
tsx
<div class="card elevated">...</div>Reactive class:
tsx
<div class={() => this.active ? 'card active' : 'card'}>...</div>Inline styles
tsx
<div style={{ color: 'red', fontSize: '16px' }}>...</div>
{/* Reactive */}
<div style={() => ({ opacity: this.visible ? 1 : 0 })}>...</div>Fragments
Group elements without a wrapper node:
tsx
render() {
return (
<>
<Header />
<Main />
<Footer />
</>
)
}ref
Use createRef() from @praxisjs/composables to get a reference to a DOM element:
tsx
import { createRef } from '@praxisjs/composables'
@Component()
class InputFocus extends StatefulComponent {
input = createRef<HTMLInputElement>()
onMount() {
this.input.el?.focus()
}
render() {
return <input ref={this.input} />
}
}