react-directive-attributes
This package is inspired by Angular’s attribute directive. You can check it out here. TLDR: it’s a cool way to add behavior to a component from the markup. You bind and configure it via an attribute, ex:
<MyTextComponent copyToClipboard="https://my-link.com">My Link</MyTextComponent>
The implementation of copyToClipboard
would wrap the MyTextComponent
to provide the extra behavior. You can write it as a simple wrapper like so:
<CopyToClipboard content="https://my-link.com">
<MyTextComponent>My Link</MyTextComponent>
</CopyToClipboard>
This package allows you to do the same but in React.
Warning
This package was made in a little over 1 hour. It does not support SSR, it does not work with JSX Transform, it might be an anti-pattern, it might affect performance and it only works with Typescript. It works by hacking React’s built-in createElement
to inject a custom component when a directive is detected (check src/react.ts
).
That being said, it’s enough to prove the concept and, if interest for the project exists, I will implement it properly. So, don’t be shy, create an issue and tell me what you want from this project.
How to implement a directive
- Install the hook. This needs to exist before any component is rendered. I recommend doing this in your entry point right after importing
React
. It might look something like this.
import {installDirectiveHandler} from "react-directive-attributes";
import React from "react";
installDirectiveHandler(React); // this line is important
import {createRoot} from "react-dom/client";
import {App} from "./App";
const root = createRoot(document.getElementById("root")!);
root.render(<App />);
- Create a global attribute type for your directive. You can do this by extending
Attributes
interface in theReact
namespace. I recommend to create a dedicated fileattributes.d.ts
just for this.
declare namespace React {
interface Attributes {
abcd?: string;
}
}
- Create the directive implementation (this is just a wrapper component). You can bind it’s params to the type of the attribute by using
DirectiveAttributeBinding<"<attribute name>">
.
import React from "react";
import {DirectiveAttributeBinding} from "react-directive-attributes";
export function AbcdDirective({value, children}: DirectiveAttributeBinding<"abcd">) {
return (
<>
<p>wrapped by directive abcd with value {value}</p>
{children}
</>
);
}
- Bind you directive to an attribute. Again, make sure this is called before you render anything. The entry point is a good place to do this. You can create an
directive.ts
and bind all your directives there.
import {bindDirectiveToAttribute} from "react-directive-attributes";
import {AbcdDirective} from "./abcd";
bindDirectiveToAttribute(AbcdDirective, "abcd");
- Done. You can now use your directive.
<MyComponent abcd="hello" />
Usage with CRA (create-react-app)
If you use a newer version of CRA, you must disable the JSX Transform and roll back to the “classic” version.
- Modify your
tsconfig.json
to match:
"jsx": "react",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment"
- Set the
DISABLE_NEW_JSX_TRANSFORM
totrue
when starting or building the application:
DISABLE_NEW_JSX_TRANSFORM=true yarn start
If this does not work, you might also need to eject.