-
-
Notifications
You must be signed in to change notification settings - Fork 117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(jsx): correct behaviour #690
Changes from all commits
33d06cd
5b1c405
b862087
bd7143b
bcf84a4
5ee069f
83efb8f
95b379f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,12 @@ | ||
This is an **EXPERIMENTAL** package that allows you to describe native DOM elements using JSX. The cool thing is that you can assign atoms and actions to the native properties, and they will be bound correctly. | ||
An **EXPERIMENTAL** JSX runtime for describing dynamic DOM UIs with Reatom. | ||
|
||
## Installation | ||
|
||
```sh | ||
npm i @reatom/jsx | ||
npm install @reatom/jsx | ||
``` | ||
|
||
`tsconfig.json` | ||
`tsconfig.json`: | ||
|
||
```json | ||
{ | ||
|
@@ -17,7 +17,7 @@ npm i @reatom/jsx | |
} | ||
``` | ||
|
||
`vite.config.js` | ||
`vite.config.js`: | ||
|
||
```js | ||
import { defineConfig } from 'vite' | ||
|
@@ -31,7 +31,9 @@ export default defineConfig({ | |
}) | ||
``` | ||
|
||
## Usage | ||
## Example | ||
|
||
Define a component: | ||
|
||
```ts | ||
import { atom, action } from '@reatom/core' | ||
|
@@ -43,26 +45,99 @@ const onInput = action((ctx, event) => | |
export const Input = () => <input value={inputAtom} oninput={onInput} /> | ||
``` | ||
|
||
In the app root. | ||
Render it: | ||
|
||
```tsx | ||
import { connectLogger } from '@reatom/framework' | ||
import { ctx, mount } from '@reatom/jsx' | ||
import { App } from './App' | ||
|
||
if (import.meta.env.DEV) { | ||
connectLogger(ctx) | ||
const disconnect = connectLogger(ctx) | ||
} | ||
|
||
mount(document.getElementById('app')!, <App />) | ||
``` | ||
|
||
## Limitations | ||
## Reference | ||
|
||
- No SSR support | ||
- No SVG support | ||
- No keyed lists support | ||
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 attributes. Add `field:` prefix to a prop name to set element fields instead of attributes. It can be used to set stuff like `HTMLInputElement..indeterminate` and `SVGPathElement..d`. There are props that treated as fields by default: | ||
|
||
- `className` (for compatibility with React libraries like [`stylerun`](https://github.com/artalar/stylerun)) | ||
- `innerHTML` | ||
- `innerText` | ||
|
||
Atom-valued props create a reactive binding to an element's attribute/field. The binding is automatically disposed when the element is disconnected from the DOM. | ||
|
||
`children` prop specifies the inner content of an element, which can be one of the following: | ||
|
||
- `false`/`null`/`undefined` to render nothing | ||
- a string, a number, or `true` to create a text node | ||
- a native DOM node to insert it as-is | ||
|
||
### Handling events | ||
|
||
Use `on*` props to add event handlers. Passed functions are automatically bound to a relevant `Ctx` value: `oninput={(ctx, event) => setValue(ctx, event.currentTarget.value)}`. | ||
|
||
### 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). | ||
|
||
### Dynamic props | ||
|
||
There's a special `$props` prop which can be used to spread props reactively: `<div someStaticProp $props={atom(ctx => getReactiveProps(ctx))}>`. Passing an array of atoms is also possible. Note that `$props` can't be used to set event handlers. | ||
|
||
### SVG | ||
|
||
To create elements with names within the SVG namespace, you should prepend `svg:` to the tag name: | ||
|
||
```tsx | ||
const anSvgElement = ( | ||
<svg:svg> | ||
<svg:path field:d="???" /> | ||
</svg:svg> | ||
) | ||
``` | ||
|
||
### Lifecycle | ||
|
||
In Reatom, every atom has lifecycle events to which you can subscribe with `onConnect`/`onDisconnect` functions. `@reatom/jsx` lets you toBy default, components don't have an atom associated with them, but you may wrap the component code in an atom manually to achieve the same result: | ||
|
||
```tsx | ||
import { onConnect, onDisconnect } from '@reatom/hooks' | ||
|
||
const MyWidget = () => { | ||
const lifecycle = atom((ctx) => <div>Something inside</div>) | ||
|
||
onConnect(lifecycle, (ctx) => console.log('component connected')) | ||
onDisconnect(lifecycle, (ctx) => console.log('component disconnected')) | ||
|
||
About the last one: When you create an element (`<Element />`), it renders once, binds all passed atoms and actions, and will not render anymore. All changes will be propagated exactly to the element's properties. However, if you need to describe conditional logic, you can put an element in the atom and achieve rerenders through this. There is no "virtual DOM," so the elements will be recreated with each render, but this could be acceptable for some cases. | ||
return lifecycle | ||
} | ||
``` | ||
|
||
Here is an example: [https://github.com/artalar/reatom-jsx/blob/main/src/App.tsx](https://github.com/artalar/reatom-jsx/blob/main/src/App.tsx) | ||
Because the pattern used above is somewhat verbose and excessive atoms may cause performance overhead, `@reatom/jsx` lets you to subscribe to lifecycle hooks of an atom, created by the library internally: | ||
|
||
```tsx | ||
const MyWidget = () => { | ||
return ( | ||
<div | ||
onConnect={(ctx) => console.log('component connected')} | ||
onDisconnect={(ctx) => console.log('component disconnected')} | ||
> | ||
Comment on lines
+129
to
+130
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
However, the hook bindings to the returned element are very weird (poor semantics), and we should find another way. A possible compromise solution could be the |
||
Something inside | ||
</div> | ||
) | ||
} | ||
``` | ||
|
||
Special `onConnect` and `onDisconnect` props pass their value to the relevant lifecycle hook, if provided. | ||
|
||
## Limitations | ||
|
||
- No DOM-less SSR (requires a DOM API implementation like `linkedom` to be provided) | ||
- No keyed lists support |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,7 +38,8 @@ | |
"@reatom/core": ">=3.5.0", | ||
"@reatom/lens": "^3.6.2", | ||
"@reatom/utils": "^3.5.0", | ||
"csstype": "^3.1.2" | ||
"csstype": "^3.1.2", | ||
"linkedom": "^0.16.4" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Move it to |
||
}, | ||
"author": "artalar", | ||
"maintainers": [ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's avoid
innerHTML
andinnerText
defaults as they are very rarely used in regular application usage.className
bindings may be an extra thing here too; I will think about it.