Anyone know how to properly add/extend all native HTML element attributes with custom ones?
With the TypeScript documentation for merging interfaces, I thought that I could just do this:
interface HTMLElement { block?: BEM.Block; element?: BEM.Element; modifiers?: BEM.Modifiers; } <div block="foo" />; // error
But I get the following Intellisense error in vscode 1.6.1 (latest):
[ts] Property 'block' does not exist on type 'HTMLProps'.
The HTMLProps
to which they are referring is React.HTMLProps<T>
and the div
element is declared to use it like so:
namespace JSX { interface IntrinsicElements { div: React.HTMLProps<HTMLDivElement> } }
I tried redeclaring the div
, but to no avail.
Related: https://github.com/Microsoft/TypeScript/issues/11684
Edit: Here's what ended up working for me:
declare module 'react' { interface HTMLAttributes<T> extends DOMAttributes<T> { block?: string element?: string modifiers?: Modifiers // <-- custom interface } }
To add custom HTML attributes in React, we can just add them as we do with regular HTML element attributes. We just add the custom-attribute custom attribute and it'll be rendered in the HTML. This works since React 16.
If you want to define your own custom attributes in HTML, you can implement them through data-* format. * can be replaced by any of your names to specify specific data to an element and target it in CSS, JavaScript, or jQuery.
So how do we add custom attributes to HTML elements? We can create any custom attribute, by adding, data-, followed by the attribute name.
Looks like in older versions of type definition files (v0.14) the interfaces were simply declared under a global React namespace, so previously you could use the standard merging syntax.
declare namespace React { interface HTMLProps<T> extends HTMLAttributes, ClassAttributes<T> { } }
However the new version of d.ts file (v15.0) have declared everything inside a module. Since modules do not support merging, to the best of my knowledge the only option right now seems to be module augmentation
: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
I did the following experiment and it worked for me:
import * as React from 'react'; declare module 'react' { interface HTMLProps<T> { block?:string; element?:string; modifiers?:string; } } export const Foo = () => { return ( <div block="123" element="456"> </div> ) };
Obviously this is quite tedious, you could put the augmentation code in another file as shown in the example from the typescript handbook, and import it:
import * as React from 'react'; import './react_augmented';
But it's still quite dirty. So maybe it's best to address the issue with the contributors of the type definition file.
I wanted to use glamor's createElement replacement which adds a css
prop to every element.
To add to the accepted answer, module augmentation seems to do the trick but HTMLProps
only worked for non-input elements. The correct interfaces to extend seems to be HTMLAttributes
and SVGAttributes
.
declare module 'react' { interface HTMLAttributes<T> { css?: any } interface SVGAttributes<T> { css?: any } }
To avoid importing the module augmentation in every component, re-export createElement:
// createElement.ts import { createElement } from 'glamor/react' declare module 'react' { interface HTMLAttributes<T> { css?: any } interface SVGAttributes<T> { css?: any } } export default createElement
Then tell TS to use our createElement
for JSX with this tsconfig:
{ "compilerOptions": { "jsx": "react", "jsxFactory": "createElement" } }
Usage:
// MyComponent.tsx import createElement from './createElement' export default function MyComponent() { return <div css={{ color: 'red' }} /> }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With