Skip to content

jsx

An EXPERIMENTAL JSX runtime for describing dynamic DOM UIs with Reatom.

Core benefits

  • No extra build step needed; we use plain TSX (JSX), which is currently supported in various tools.
  • Nice integrations with the platform; <div /> returns the real element.
  • Rerender-less architecture with direct reactive bindings, which means extreme performance!
  • Only 1kb runtime script (excluding the tiny core package).
  • Built-in CSS management with a simple API and efficient CSS variables usage.

Installation

You can use @reatom/core instead of the framework, but we highly recommend using the framework to access the maximum features of Reatom.

Terminal window
npm install @reatom/framework @reatom/jsx

tsconfig.json:

{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "@reatom/jsx"
}
}

vite.config.js:

import { defineConfig } from 'vite'
export default defineConfig({
esbuild: {
jsxFactory: 'h',
jsxFragment: 'hf',
jsxInject: `import { h, hf } from "@reatom/jsx";`,
},
})

Example

Advanced example with dynamic entities you can find here: https://github.com/artalar/reatom/tree/v3/examples/reatom-jsx

Define a component:

import { atom, action } from '@reatom/core'
export const inputAtom = atom('')
const onInput = action((ctx, event) =>
inputAtom(ctx, event.currentTarget.value),
)
export const Input = () => <input value={inputAtom} on:input={onInput} />

Render it:

import { connectLogger } from '@reatom/framework'
import { ctx, mount } from '@reatom/jsx'
import { App } from './App'
if (import.meta.env.MODE === 'development') {
connectLogger(ctx)
}
mount(document.getElementById('app')!, <App />)

You can create ctx manually and use it to create a scoped render instance with reatomJsx.

Reference

This library implements a common TypeScript JSX factory that creates and configures native DOM elements.

By default, props passed to the JSX factory are set as properties. Add attr: prefix to the name to set element attribute instead of property.

For all kinds of properties you can pass a primitive value or an atom with a primitive value.

The children prop specifies the inner content of an element, which can be one of the following:

  • false/null/undefined to render nothing
  • a string or a number to create a text node
  • a native DOM node to insert it as-is
  • an atom or a function returning any option listed above

Handling events

Use on:* props to add event handlers. Passed functions are automatically bound to a relevant Ctx value: on:input={(ctx, event) => setValue(ctx, event.currentTarget.value)}.

Models

For simple AtomMut bindings to the native input you can use model:value syntax, where “value” could be: value, valueAsNumber, checked.

export const inputAtom = atom('')
export const Input = () => <input model:value={inputAtom} />

By the way, you can safely create any needed resources inside a component body, as it calls only once when it created.

export const Input = () => {
export const input = atom('')
return <input model:value={input} />
}

Styles

Object-valued style prop applies styles granularly: style={{top: 0, display: equalsFalseForNow && 'none'}} sets top: 0;.

false, null and undefined style values remove the property. Non-string style values are stringified (we don’t add px to numeric values automatically).

CSS-in-JS

We have a minimal, intuitive, and efficient styling engine tightly integrated with components. You can set a styles in css prop and all relative css-variables to css:variable-name prop.

The example below is correctly formatted by Prettier and has syntax highlighting provided by the ‘vscode-styled-components’ extension

export const HeaderInput = ({ size = 0 }) => {
const input = atom('')
const size = atom((ctx) => ctx.spy(input).length)
return (
<input
model:value={input}
css:size={size}
css={`
font-size: calc(1em + var(--size) * 0.1em);
`}
/>
)
}

Under the hood, it will create a unique class name and will be converted to this code:

export const HeaderInput = ({ size = 0 }) => {
const input = atom('')
const size = atom((ctx) => ctx.spy(input).length)
return (
<input
className={createAndInsertClass(`
font-size: calc(1em + var(--size) * 0.1em);
`)}
style={atom((ctx) => ({
'--size': ctx.spy(size),
}))}
/>
)
}

Components

Components in @reatom/jsx are just functions returning JSX elements. They neither have state nor any lifecycle associated with them. Because component instantiation boils down into function calls, features like $spread are not supported in them.

Spreads

In Reatom, there is no concept of “rerender” like React. Instead, we have a special $spread prop that can be used to spread props reactively.

<div
$spread={atom((ctx) =>
ctx.spy(valid)
? { disabled: true, readonly: true }
: { disabled: false, readonly: false },
)}
/>

SVG

To create elements with names within the SVG namespace, you should prepend svg: to the tag name:

const anSvgElement = (
<svg:svg>
<svg:path d="???" />
</svg:svg>
)

Limitations

These limitations will be fixed in the feature

  • No DOM-less SSR (requires a DOM API implementation like linkedom to be provided)
  • No keyed lists support