When building a Laravel Inertia React application with TypeScript, I encountered an issue where custom web components were not recognized by TypeScript, even after importing and defining their types. This post documents how I resolved this issue for my Rich Text Editor component, but the solution applies to any custom web component.
The Problem
TypeScript does not natively recognize custom HTML elements like . To fix this, we need to extend the JSX.IntrinsicElements interface to include our custom component and define its valid attributes.
Solution: Extending JSX.IntrinsicElements
Add the following declaration to your project to define the custom component and its attributes:
declare global {
namespace JSX {
interface IntrinsicElements {
"rich-text-editor": {
placeholder?: string;
class?: string;
ref?: React.Ref<HTMLElement>;
};
}
}
}
In the above code I have used my “rich-text-editor” component and also defined the valid attributes. If you don’t want to enforce strict type checking for the custom component’s attributes, you can use any instead. However, this approach sacrifices type safety and is not recommended for production code.
declare global {
namespace JSX {
interface IntrinsicElements {
"rich-text-editor": any;
}
}
}
Using the Custom Component in React
After defining the custom component, you can use it in your React code. Below is an example of a wrapper component for the :
// RichTextEditorWrapper.tsx
import React, { useRef, useEffect, forwardRef } from 'react';
import './rich-text-editor.js';
interface RichTextEditorProps {
value?: string;
onChange?: (value: string) => void;
placeholder?: string;
className?: string;
}
// This wrapper allows React to communicate with your custom element
const RichTextEditorWrapper = forwardRef<HTMLElement, RichTextEditorProps>(
({ value, onChange, placeholder, className }, ref) => {
const editorRef = useRef<HTMLElement | null>(null);
useEffect(() => {
// Get reference to the actual DOM element
const editor = editorRef.current;
if (!editor) return;
// Set initial value
if (value !== undefined && editor.innerHTML !== value) {
editor.innerHTML = value;
}
// Add event listener for content changes
const handleContentChange = (event: Event) => {
const customEvent = event as CustomEvent;
if (onChange && customEvent.detail) {
onChange(customEvent.detail.value);
}
};
// Listen for custom events your web component might dispatch
editor.addEventListener('content-change', handleContentChange);
return () => {
// Cleanup
editor.removeEventListener('content-change', handleContentChange);
};
}, [value, onChange]);
return (
<rich-text-editor
ref={(el) => {
editorRef.current = el;
if (typeof ref === 'function') ref(el);
else if (ref) ref.current = el;
}}
placeholder={placeholder}
class={className}
/>
);
}
);
export default RichTextEditorWrapper;
Example Usage
Here’s how you can use the RichTextEditorWrapper
in a React application:
import React, { useState } from 'react';
import RichTextEditorWrapper from './RichTextEditorWrapper';
const App = () => {
const [content, setContent] = useState('');
const handleChange = (value: string) => {
setContent(value);
};
return (
<div>
<RichTextEditorWrapper
value={content}
onChange={handleChange}
placeholder="Enter your text here"
className="editor"
/>
<div>
<h3>Preview:</h3>
<div dangerouslySetInnerHTML={{ __html: content }} />
</div>
</div>
);
};
export default App;
Restart the TypeScript Server
After making these changes, you may need to restart the TypeScript server for the changes to take effect. In Visual Studio Code, press Ctrl + Shift + P (or Cmd + Shift + P on macOS), type “Restart TypeScript Server,” and select the option from the dropdown.
Common Errors and Fixes
- Error:
Property 'rich-text-editor' does not exist on type 'JSX.IntrinsicElements'
- Ensure the
declare global
block is correctly added to your project and that the TypeScript server has been restarted.
- Ensure the
- Error:
Cannot find module './rich-text-editor.js'
- Ensure the path to the custom component is correct and that the file exists.
Conclusion
By extending the JSX.IntrinsicElements interface, you can seamlessly integrate custom web components into your TypeScript-based React application. This approach ensures type safety and improves developer experience. If you encounter any issues or have suggestions for improvement, feel free to leave a comment below. Happy coding!