diff --git a/src/routes/reference/jsx-attributes/use.mdx b/src/routes/reference/jsx-attributes/use.mdx index b622f82c2..ebfc504f4 100644 --- a/src/routes/reference/jsx-attributes/use.mdx +++ b/src/routes/reference/jsx-attributes/use.mdx @@ -3,38 +3,91 @@ title: use:* order: 5 --- -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: +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. + +A directive is a function with the following signature ```ts -function directive(element: Element, accessor: () => any): void +function directive(element: HTMLElement, accessor: Accessor): void; ``` 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. +## Example + +A `model` directive for two-way data binding + ```tsx -const [name, setName] = createSignal("") +import type { Accessor, Signal } from "solid-js"; -function model(el, value) { - const [field, setField] = value() - createRenderEffect(() => (el.value = field())) - el.addEventListener("input", (e) => setField(e.target.value)) -}; +function model(element: HTMLInputElement, value: Accessor>) { + const [field, setField] = value(); + createRenderEffect(() => (element.value = field())); + element.addEventListener("input", ({ target }) => setField(target.value)); +} + +const [name, setName] = createSignal(""); - +; ``` -To register with TypeScript extend the JSX namespace. +## TypeScript Support + +To type custom directives, extend the `DirectiveFunctions` interface ```ts +declare module "solid-js" { + namespace JSX { + interface DirectiveFunctions { + model: typeof model; + } + } +} +``` + +If you just want to constrain the second argument to the directive function, you can extend the older `Directives` interface + +```tsx declare module "solid-js" { namespace JSX { interface Directives { - model: [() => any, (v: any) => any] + model: Signal; } } } ``` +## Avoiding Tree-Shaking + +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`. +To prevent this: + +1. Use the `onlyRemoveTypeImports: true` option in `babel-preset-typescript`. For `vite-plugin-solid`, add this to `vite.config.ts` + + ```ts + import solidPlugin from "vite-plugin-solid"; + + export default { + plugins: [ + solidPlugin({ + typescript: { onlyRemoveTypeImports: true } + }) + ], + }; + ``` + + Note: This requires consistent use of `export type` and `import type` in your codebase to avoid issues. + +2. Add a fake access like `false && d;` in the module + + ```tsx + import { model } from "./directives"; + false && model; // Prevents tree-shaking + ; + ``` + + This is removed by bundlers like Terser, unlike a plain `model;` which may remain in the bundle. + :::caution[Limitations] Directives only work with native HTML elements (HTML/SVG/MathML/Custom Elements). Directives are not forwarded and **won't work in user defined components**, such as `` [see also](https://github.com/solidjs/solid/discussions/722)