Skip to content

Commit 819a1a1

Browse files
Addresses #547 (#1253)
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent 1d849a7 commit 819a1a1

File tree

1 file changed

+64
-11
lines changed
  • src/routes/reference/jsx-attributes

1 file changed

+64
-11
lines changed

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

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,91 @@ title: use:*
33
order: 5
44
---
55

6-
These are custom directives. In a sense this is just syntax sugar over ref but allows us to easily attach multiple directives to a single element. A directive is a function with the following signature:
6+
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.
7+
8+
A directive is a function with the following signature
79

810
```ts
9-
function directive(element: Element, accessor: () => any): void
11+
function directive(element: HTMLElement, accessor: Accessor<any>): void;
1012
```
1113

1214
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.
1315

16+
## Example
17+
18+
A `model` directive for two-way data binding
19+
1420
```tsx
15-
const [name, setName] = createSignal("")
21+
import type { Accessor, Signal } from "solid-js";
1622

17-
function model(el, value) {
18-
const [field, setField] = value()
19-
createRenderEffect(() => (el.value = field()))
20-
el.addEventListener("input", (e) => setField(e.target.value))
21-
};
23+
function model(element: HTMLInputElement, value: Accessor<Signal<string>>) {
24+
const [field, setField] = value();
25+
createRenderEffect(() => (element.value = field()));
26+
element.addEventListener("input", ({ target }) => setField(target.value));
27+
}
28+
29+
const [name, setName] = createSignal("");
2230

23-
<input type="text" use:model={[name, setName]} />
31+
<input type="text" use:model={[name, setName]} />;
2432
```
2533

26-
To register with TypeScript extend the JSX namespace.
34+
## TypeScript Support
35+
36+
To type custom directives, extend the `DirectiveFunctions` interface
2737

2838
```ts
39+
declare module "solid-js" {
40+
namespace JSX {
41+
interface DirectiveFunctions {
42+
model: typeof model;
43+
}
44+
}
45+
}
46+
```
47+
48+
If you just want to constrain the second argument to the directive function, you can extend the older `Directives` interface
49+
50+
```tsx
2951
declare module "solid-js" {
3052
namespace JSX {
3153
interface Directives {
32-
model: [() => any, (v: any) => any]
54+
model: Signal<string>;
3355
}
3456
}
3557
}
3658
```
3759

60+
## Avoiding Tree-Shaking
61+
62+
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`.
63+
To prevent this:
64+
65+
1. Use the `onlyRemoveTypeImports: true` option in `babel-preset-typescript`. For `vite-plugin-solid`, add this to `vite.config.ts`
66+
67+
```ts
68+
import solidPlugin from "vite-plugin-solid";
69+
70+
export default {
71+
plugins: [
72+
solidPlugin({
73+
typescript: { onlyRemoveTypeImports: true }
74+
})
75+
],
76+
};
77+
```
78+
79+
Note: This requires consistent use of `export type` and `import type` in your codebase to avoid issues.
80+
81+
2. Add a fake access like `false && d;` in the module
82+
83+
```tsx
84+
import { model } from "./directives";
85+
false && model; // Prevents tree-shaking
86+
<input type="text" use:model={[name, setName]} />;
87+
```
88+
89+
This is removed by bundlers like Terser, unlike a plain `model;` which may remain in the bundle.
90+
3891
:::caution[Limitations]
3992
Directives only work with native HTML elements (HTML/SVG/MathML/Custom Elements).
4093
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)

0 commit comments

Comments
 (0)