Skip to content

Commit 759a60b

Browse files
committed
jsx event and directive reference pages
1 parent 921e3b1 commit 759a60b

4 files changed

Lines changed: 121 additions & 131 deletions

File tree

src/routes/reference/jsx-attributes/on.mdx

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,51 +2,67 @@
22
title: "on:*"
33
order: 4
44
use_cases: >-
5-
custom events, non-bubbling events, capture phase handling, passive listeners,
6-
special event options
5+
direct event listeners, custom event names, event listener options
76
tags:
87
- events
98
- listeners
109
- dom
1110
- capture
1211
- passive
13-
- handlers
1412
version: "1.0"
1513
description: >-
16-
Attach non-delegated event handlers with on:* in SolidJS. Control capture,
17-
passive, and once options for advanced event handling requirements.
14+
Attach an event listener directly to an element with `addEventListener`.
1815
---
1916

20-
For events with capital letters, listener options, or if you need to attach event handlers directly to a DOM element instead of optimized delegating via the document, use `on:*` in place of `on*`.
17+
`on:*` attaches an event listener directly to an element with `addEventListener`.
18+
19+
## Syntax
2120

2221
```tsx
23-
<div on:DOMContentLoaded={(e) => console.log("Welcome!")} />
22+
<div on:wheel={handler} />
2423
```
2524

26-
This directly attaches an event handler (via [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)) to the `div`.
25+
## Value
2726

28-
:::note
27+
- **Type:** event handler function or `EventListenerObject & AddEventListenerOptions`
2928

30-
<span>New in v1.9.0</span>
31-
:::
29+
Listener passed to `addEventListener`.
30+
31+
## Behavior
32+
33+
- `on:name={handler}` attaches a listener for the event named `name`.
34+
- The listener is attached directly to the element instead of using Solid's delegated event system.
35+
- Event names keep the text after `on:` exactly as written.
36+
- Listener options such as `once`, `passive`, `capture`, and `signal` can be provided by passing an object that implements `handleEvent`.
37+
38+
## Examples
39+
40+
### Basic usage
3241

33-
An aditional special syntax that allows full control of [`capture`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#capture), [`passive`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#passive), [`once`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#once) and [`signal`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#signal) is an intersection or combination of `EventListenerObject` & `AddEventListenerOptions`, as follows:
42+
```tsx
43+
<div on:DOMContentLoaded={(e) => console.log("Welcome!", e)} />
44+
```
45+
46+
### Listener options
3447

3548
```tsx
3649
const handler = {
3750
handleEvent(e) {
38-
console.log(e)
51+
console.log(e);
3952
},
40-
once:true,
41-
passive:false,
42-
capture:true
43-
}
53+
once: true,
54+
passive: false,
55+
capture: true,
56+
};
4457

45-
<div on:wheel={handler} />
58+
<div on:wheel={handler} />;
59+
```
4660

47-
// or inline
61+
:::note
62+
The object-listener form was added in Solid 1.9.0.
63+
:::
4864

49-
<div on:click={{passive:true, handleEvent(e) { console.log("Weeeee!")}}} />
50-
```
65+
## Related
5166

52-
This new syntax replaces the now deprecated `oncapture:` and it's future proof for any posible new event listener options.
67+
- [`on*`](/reference/jsx-attributes/on_)
68+
- [`oncapture:*`](/reference/jsx-attributes/on)

src/routes/reference/jsx-attributes/on_.mdx

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,58 @@
22
title: on*
33
order: 3
44
use_cases: >-
5-
user interactions, click handlers, form events, keyboard input, mouse events,
6-
touch handling
5+
delegated UI events, click handlers, input handlers, keyboard handlers
76
tags:
87
- events
98
- handlers
10-
- interactions
11-
- click
12-
- input
139
- delegation
10+
- dom
1411
version: "1.0"
1512
description: >-
16-
Handle user events efficiently in SolidJS with onClick and other event
17-
handlers. Optimized delegation system for improved performance at scale.
13+
Attach an event handler with Solid's delegated event system when the event is
14+
supported.
1815
---
1916

20-
Event handlers in Solid typically take the form of `onclick` or `onClick` depending on style.
17+
`on*` attaches an event handler using Solid's delegated event system when the event is supported.
18+
19+
## Syntax
2120

2221
```tsx
23-
<div onClick={(e) => console.log(e.currentTarget)} />
22+
<div onClick={handler} />
2423
```
2524

26-
Conceptually, this example attaches a `click` event listener (via [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener)) to the `div`. However, Solid actually handles common UI events that bubble and are composed (such as `click`) at the document level, and then synthetically implements delegation (capturing and bubbling). This improves performance for these common events by reducing the number of event handlers.
25+
## Value
2726

28-
Note that `onClick` handles the event `click`; in general, event names get mapped to lower case. If you need to work with event names containing capital letters, or use listener options such once, passive, capture see [`on:`](/reference/jsx-attributes/on) which attaches event handlers directly (also avoiding fancy delegation via document).
27+
- **Type:** event handler function or `[handler, data]`
2928

30-
Solid also supports passing a two-element array to the event handler to bind a value to the first argument of the event handler. This doesn't use `bind` or create an additional closure, so it is a highly optimized way of delegating events.
29+
Event handler, or a handler/data pair for delegated binding.
3130

32-
```tsx
33-
function handler(itemId, e) {
34-
/*...*/
35-
}
31+
## Behavior
32+
33+
- Common delegated events are handled at the document level instead of attaching one listener per element.
34+
- Event names are mapped to lower case, so `onClick` listens to `click`.
35+
- The two-element array form passes the first item as bound data to the handler.
36+
- Delegated bindings are not reactive and are not rebound automatically.
37+
- For direct element listeners, custom event casing, or listener options, use [`on:*`](/reference/jsx-attributes/on).
38+
39+
## Examples
3640

37-
<ul>
38-
<For each={state.list}>{(item) => <li onClick={[handler, item.id]} />}</For>
39-
</ul>;
41+
### Basic usage
42+
43+
```tsx
44+
<div onClick={(e) => console.log(e.currentTarget)} />
4045
```
4146

42-
Events are never rebound and the bindings are not reactive, as it is expensive to attach and detach listeners. Since event handlers are called like any other function each time an event fires, there is no need for reactivity; shortcut your handler if desired.
47+
### Handler and bound data
4348

4449
```tsx
45-
// if defined, call it; otherwise don't.
46-
<div onClick={() => props.handleClick?.()} />
50+
function handler(itemId, e) {
51+
console.log(itemId, e);
52+
}
53+
54+
<For each={state.list}>{(item) => <li onClick={[handler, item.id]} />}</For>;
4755
```
4856

49-
Note that `onChange` and `onInput` work according to their native behavior (unlike, say, React). [`onInput`](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) will fire immediately after the value has changed; for most `<input>` fields, [`onChange`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) will only fire after the field loses focus. The event's `currentTarget` refers to the element that the event was attached to, while `target` gives the element that actually triggered the event (e.g. the user clicked on).
57+
## Related
58+
59+
- [`on:*`](/reference/jsx-attributes/on)

src/routes/reference/jsx-attributes/once.mdx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,39 @@
22
title: "@once"
33
order: 5
44
use_cases: >-
5-
performance optimization, static props, non-reactive values, compile-time
6-
optimization, reducing overhead
5+
non-reactive JSX expressions, compile-time opt-out from reactive wrapping
76
tags:
8-
- optimization
9-
- performance
10-
- static
117
- compiler
128
- jsx
9+
- optimization
1310
version: "1.0"
1411
description: >-
15-
Optimize SolidJS components with @once decorator for static values. Prevent
16-
unnecessary reactive wrapping and improve runtime performance.
12+
Mark a JSX expression so the compiler does not wrap it reactively.
1713
---
1814

19-
Solid's compiler uses a heuristic for reactive wrapping and lazy evaluation of JSX expressions. Does it contain a function call, a property access, or JSX? If yes we wrap it in a getter when passed to components or in an effect if passed to native elements.
15+
`/*@once*/` marks a JSX expression so the compiler does not wrap it reactively.
16+
17+
## Syntax
18+
19+
```tsx
20+
<MyComponent value={/*@once*/ expr} />
21+
```
22+
23+
## Behavior
24+
25+
- `/*@once*/` applies to the expression that follows it.
26+
- It tells the compiler not to wrap that expression in reactive access machinery.
27+
- It can be used in props and in children.
28+
29+
## Examples
2030

21-
Knowing this heuristic and its limitations, we can reduce overhead of things we know will never change by accessing them outside of the JSX. A lone variable will never be wrapped. We can also tell the compiler not to wrap them by starting the expression with a comment decorator `/* @once */`.
31+
### Prop value
2232

2333
```tsx
2434
<MyComponent static={/*@once*/ state.wontUpdate} />
2535
```
2636

27-
This also works on children.
37+
### Child value
2838

2939
```tsx
3040
<MyComponent>{/*@once*/ state.wontUpdate}</MyComponent>

src/routes/reference/jsx-attributes/use.mdx

Lines changed: 30 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,51 @@
22
title: "use:*"
33
order: 5
44
use_cases: >-
5-
complex dom interactions, tooltips, form handling, two-way data binding,
6-
reusable element behaviors, custom input components
5+
reusable DOM behavior, directives, element setup helpers
76
tags:
87
- directives
98
- dom
10-
- forms
11-
- bindings
12-
- components
13-
- typescript
9+
- jsx
1410
version: "1.0"
1511
description: >-
16-
Create custom directives in SolidJS to attach reusable behaviors to DOM
17-
elements. Perfect for tooltips, form handling, and two-way data binding.
12+
Attach a directive function to a native element with `use:*`.
1813
---
1914

20-
Custom directives attach reusable behavior to DOM elements, acting as syntactic sugar over `ref`. They’re ideal for complex DOM interactions like scrolling, tooltips, or form handling, which are cumbersome to repeat in JSX.
15+
`use:*` attaches a directive function to a native element.
2116

22-
A directive is a function with the following signature
17+
## Syntax
18+
19+
```tsx
20+
<input use:model={[value, setValue]} />
21+
```
22+
23+
## Type
2324

2425
```ts
26+
type Accessor<T> = () => T;
27+
2528
function directive(element: HTMLElement, accessor: Accessor<any>): void;
2629
```
2730

28-
Directive functions are called at render time but before being added to the DOM. You can do whatever you'd like in them including create signals, effects, register clean-up etc.
31+
## Value
2932

30-
## Example
33+
- **Type:** directive argument
3134

32-
A `model` directive for two-way data binding
35+
Value exposed to the directive through its accessor.
3336

34-
```tsx
35-
import type { Accessor, Signal } from "solid-js";
37+
## Behavior
38+
39+
- `use:name={value}` calls the directive named `name` with the element and an accessor for `value`.
40+
- Directive functions run during rendering before the element is connected to the DOM.
41+
- Directives only work on native elements, including custom elements.
42+
- Directives are not forwarded through user-defined components.
43+
44+
## Examples
3645

37-
function model(element: HTMLInputElement, value: Accessor<Signal<string>>) {
46+
### Basic usage
47+
48+
```tsx
49+
function model(element, value) {
3850
const [field, setField] = value();
3951
createRenderEffect(() => (element.value = field()));
4052
element.addEventListener("input", ({ target }) => setField(target.value));
@@ -45,64 +57,6 @@ const [name, setName] = createSignal("");
4557
<input type="text" use:model={[name, setName]} />;
4658
```
4759

48-
## TypeScript Support
49-
50-
To type custom directives, extend the `DirectiveFunctions` interface
51-
52-
```ts
53-
declare module "solid-js" {
54-
namespace JSX {
55-
interface DirectiveFunctions {
56-
model: typeof model;
57-
}
58-
}
59-
}
60-
```
61-
62-
If you just want to constrain the second argument to the directive function, you can extend the older `Directives` interface
63-
64-
```tsx
65-
declare module "solid-js" {
66-
namespace JSX {
67-
interface Directives {
68-
model: Signal<string>;
69-
}
70-
}
71-
}
72-
```
73-
74-
## Avoiding Tree-Shaking
75-
76-
When importing a directive `d` from another module and using it only as `use:d`, TypeScript (via [babel-preset-typescript](https://babeljs.io/docs/babel-preset-typescript)) may remove the import, as it doesn’t recognize `use:d` as a reference to `d`.
77-
To prevent this:
78-
79-
1. Use the `onlyRemoveTypeImports: true` option in `babel-preset-typescript`. For `vite-plugin-solid`, add this to `vite.config.ts`
80-
81-
```ts
82-
import solidPlugin from "vite-plugin-solid";
83-
84-
export default {
85-
plugins: [
86-
solidPlugin({
87-
typescript: { onlyRemoveTypeImports: true },
88-
}),
89-
],
90-
};
91-
```
92-
93-
Note: This requires consistent use of `export type` and `import type` in your codebase to avoid issues.
94-
95-
2. Add a fake access like `false && d;` in the module
96-
97-
```tsx
98-
import { model } from "./directives";
99-
false && model; // Prevents tree-shaking
100-
<input type="text" use:model={[name, setName]} />;
101-
```
102-
103-
This is removed by bundlers like Terser, unlike a plain `model;` which may remain in the bundle.
104-
105-
:::caution[Limitations]
106-
Directives only work with native HTML elements (HTML/SVG/MathML/Custom Elements).
107-
Directives are not forwarded and **won't work in user defined components**, such as `<MyComponent use:myinput={[..]}/>` [see also](https://github.com/solidjs/solid/discussions/722)
60+
:::note
61+
When using TypeScript, custom directives may require extending Solid's JSX directive typings.
10862
:::

0 commit comments

Comments
 (0)