Utilizing TypeScript Generics to Dynamically Set Tag Names in React

I am working on a straightforward polymorphic React component that is designed to render only tag names (such as span) and not custom React components (like MyComponent). I believe this can be achieved using JSX.IntrinsicElements. Here is the code snippet I have:

import React from "react";

type PolymorphicComponentProps<T extends keyof JSX.IntrinsicElements> = {
  as?: T;
} & JSX.IntrinsicElements[T];

const PolymorphicComponent = <T extends keyof JSX.IntrinsicElements = "div">({
  as: Component = "div" as T,
  ...rest
}: PolymorphicComponentProps<T>) => {
  return <Component {...rest} />; // TS error: JSX element type 'Component' does not have any construct or call signatures.
};

The generic type T will determine the HTML attributes that are available. For example, if I were to render PolymorphicComponent as an a tag...

<PolymorphicComponent as="a" />

...then I would get autocomplete for the href attribute/prop. And as another example, if I were to render PolymorphicComponent as an img tag...

<PolymorphicComponent as="img" />

...then I would get autocomplete for the src attribute/prop. While this seems to be functioning correctly, I am still facing the TypeScript error mentioned in the PolymorphicComponent component function definition:

JSX element type 'Component' does not have any construct or call signatures
.

What I can't figure out: The Component variable (alias for the as prop) has a type of T | (T & string). I would expect the type of Component to just be T.

If I create this very simple component that always renders a div, there are no TypeScript errors:

const DivComponent = () => {
  const Div: keyof JSX.IntrinsicElements = "div";
  return <Div />;
};

Question: What do I need to adjust to eliminate the TypeScript error? Based on my research, it seems like I don't need to switch the JSX with React.createElement, so I'm hoping to maintain the JSX version of this component if feasible.

Answer №1

I have been pondering about whether there is a more efficient approach, but after some experimentation, I discovered that by converting Component to React.ElementType, TypeScript works perfectly:

type IntrinsicElement = keyof JSX.IntrinsicElements;

type PolymorphicComponentProps<T extends IntrinsicElement> = {
  as?: T;
} & JSX.IntrinsicElements[T];

const PolymorphicComponent = <T extends IntrinsicElement = 'div'>({
  as: elementType = 'div' as T,
  ...rest
}: PolymorphicComponentProps<T>) => {
  const Component = elementType as ElementType;
  return <Component {...rest} />;
};

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The onClick event in monaco-editor/react is not triggering as expected

I've successfully integrated monaco-editor/react into my Next.js application, and all is working smoothly. Now, I am facing an issue with the onClick event of the editor not firing. Here's the code snippet: import Editor from "@monaco-editor ...

Struggling to modify the datetimepicker material-ui within a react js class component

I am currently using a datetimepicker component from material-ui/pickers in a class-based react-js component. I am facing an issue where the datetimepicker closes immediately every time I click on any part of it (date, year, etc.). Here's a snippet of ...

What is the best way to create mock icons for all the React `@material-ui/icons` using Jest?

Can this query be broadened to ask - what is the best way to simulate all attributes on a module that has been imported in order to produce React components? ...

How to access the onchange text in a react-select search component

I'm currently working on implementing search select functionality in my webpage using the react-select-search npm package. This is my main component: import React, { Component } from "react"; import Task from "./task"; // Rest of ...

Cannot assign Angular 4 RequestOptions object to post method parameter

I'm having trouble with these codes. Initially, I created a header using the code block below: headers.append("Authorization", btoa(username + ":" + password)); var requestOptions = new RequestOptions({ headers: headers }); However, when I tried to ...

Question about TypeScript annotations: arrays containing key-value pairs

Is there an explanation for why this issue occurs in VSCode? interface Point { x: number; y: number; } let grid: [key: number, value: [key: number, value: Point]]; // ... // Accessing an object of type number | [key: number, value: Point] var c ...

Create a union type by utilizing indices of an array type

For instance: type BasicTheme = { name: 'basic'; colors: [string, string]; }; type AdvancedTheme = { name: 'advanced'; colors: [string, string, string, string]; }; type MainColor = ???; // 'main-1' | 'main-2&apo ...

Switch Focus and Collapse Submenus upon Menu Click in Recursive React Menu

I've created a dynamic menu system in React using Material-UI that supports recursion for submenus. I'm aiming to implement the following features: 1. When a menu item is clicked, all other open submenus should close and focus on the clicked men ...

A guide on updating various states using React Hooks

Creating a background component with the use of Vanta in NextJS, here's the code snippet: import { useEffect, useRef, useState } from "react"; import * as THREE from "three"; import FOG from "vanta/dist/vanta.fog.min"; im ...

updating rows in a table

Currently, I have a grid array filled with default data retrieved from the database. This data is then displayed on the front end in a table/grid format allowing users to add and delete rows. When a row is added, I only want to insert an empty object. The ...

Combine es6 imports from the identical module using an Eslint rule or plugin

Looking to consolidate my ES6 imports from a single module into one for my React project. For example: import { Title } from "@mantine/core"; import { Center } from "@mantine/core"; import { Divider } from "@mantine/core"; T ...

Fetching data from the server in NextJS based on user input

After assembling a Client and a Server component (using App Router), I was able to refresh the Server when the user interacts with it, triggering a route refresh by using: router.push(pathname) Although that worked well for refreshing the Server, now I ne ...

What causes certain components in ReactJs to not respond to the `:focus` pseudo-class?

Currently, I am in the process of implementing a straightforward Emoji-Rating system where 5 emojis are displayed on the screen and upon hovering over them, their appearance changes. This functionality is achieved using the :hover pseudo-class. My goal is ...

Exploring the implementation of --history-api-fallback in webpack

let path = require('path') module.exports = { entry:path.resolve('public/src/index.js'), output: { path:__dirname + "/public", filename: "bundle.js" }, module: { loaders: [{ exclude: / ...

MapboxGL showcases user location with poor accuracy

I am currently developing a Progressive Web App (PWA) that involves users navigating through a map and collecting rewards by visiting specific store locations. To implement the map functionality, I have been utilizing a React wrapper for Mapbox, which has ...

What is the best way to configure the default entry point for a package.json file in a React

I'm having trouble with the default export in my package.json file. when I try to import: import { Component } from 'packagename/'; // size 22kb or import { Component } from 'packagename/dist' // size 22kb; but import { Component ...

Is there a way to execute react-data-grid samples on jsfiddle?

I recently came across react-data-grid and I've been exploring the examples on their website. However, when I try to access the "play around with it" link that redirects to jsfiddle, I'm encountering issues viewing the output. Can anyone suggest ...

What is the most effective way to compare a nested array using the map or filter function in order to return only the first match

Here is a code snippet showcasing the data object containing information for the codeworks array with code and text values. There is a key array named code = ["ABC","MDH"] and the expected output is displayed in the following code snippets. const data = ...

Select one option from the array of checkboxes

My goal is to retrieve an array of allergies from a URL and create a checkbox for each allergy. However, when I click on any of them, the checkbox does not get checked even though the underlying array of allergies is updated with the "checked" attribute ch ...

Utilizing a custom hook alongside another hook in React - a streamlined approach?

I am currently developing an app using React with next.js. There is a GraphQL query that I need to run in order to retrieve some data, but I seem to be encountering some issues. When I use the useQuery hook as shown below, it successfully returns the res ...