Home > Mobile >  Using a string as a component name in react and typescript
Using a string as a component name in react and typescript

Time:12-24

I have an input component that can either render a textarea component (from a library) or a regular input. This is the component:

import React, { useEffect, useRef, useState } from 'react'
import './AppInput.css'

interface Props {
    placeholder: string,
    value: string | number,
    setValue: React.Dispatch<React.SetStateAction<string>>,
    text: string,
    shouldFocusOnKey: boolean,
    type: string,
    name: string,
    className: string,
    Component: string
}

export const AppInput: React.FC<Props> = (props) => {
    const { 
        placeholder, 
        value, 
        setValue, 
        text, 
        shouldFocusOnKey, 
        type = 'text', 
        name, 
        className, 
        Component='input' } = props
    let inputRef = useRef<HTMLInputElement>(null)

    useEffect(() => {
        if (shouldFocusOnKey) {
            window.onkeydown = (e) => {
                if (e.ctrlKey && e.key === 'm') {
                     inputRef.current?.focus()
                }
            }
        }
    }, [shouldFocusOnKey])

    return (
        <div className={`appinput ${className}`}>
            <span>{text}</span>
            <div className="flexcol">
                <Component 
                    ref={inputRef} 
                    placeholder={placeholder} 
                    value={value} 
                    onChange={(e) => { 
                        setValue(e.target.value)
                    }} 
                    type={type} 
                    name={name} />
            </div>
        </div>
    )
}

In the case that Component === 'ReactTextareaAutosize' Then I want to render that component as depicted.This works perfectly well in ReactJS. But when using ReactTS I get type errors etc as typescript does not understand what I am trying to do or doing. Any idea how I can solve this?

CodePudding user response:

You can use Code Sample

CodePudding user response:

Apart from the useRef type, simply resolve the actual component from the value of your Component prop, so that it is no longer a simple string, but either an input or an actual ReactTextareaAutosize React Component:

import ReactTextareaAutosize from 'react-textarea-autosize'

interface Props {
    // other props
    Component: 'input' | 'ReactTextareaAutosize'
}

export const AppInput: React.FC<Props> = (props) => {
    const {
        // other props
        Component = 'input' } = props

    const ActualComponent = Component === 'ReactTextareaAutosize' ? ReactTextareaAutosize : Component

    return (
        <div>
            <div className="flexcol">
                <ActualComponent />
            </div>
        </div>
    )
}

Playground Link


As for the useRef type, if you just need for simple usage like .focus(), i.e. common properties/methods of <input> and <textarea>, then the easiest would be to type it as the intersection HTMLInputElement & HTMLTextAreaElement, so that it is compatible with both elements, and you can use all common properties/methods:

const inputRef = useRef<
  HTMLInputElement & HTMLTextAreaElement
>(null)

inputRef.current?.focus() // Okay

<ActualComponent
  ref={inputRef} // Okay
/>

Playground Link


Now if you absolutely need to be able to use specific properties and/or methods to <input> and <textarea> elements, then things get more verbose just for the sake of safe typing unfortunately.

The most straightforward technique is to separate the ref and actual component for each possibility:

const inputRef = useRef<HTMLInputElement>(null)
const textareaRef = useRef<HTMLTextAreaElement>(null)

inputRef.current?.focus() // Okay
textareaRef.current?.focus() // Okay

<div className="flexcol">
  {Component === 'input'
    ? <input
        ref={inputRef} // Okay
      />
    : <ReactTextareaAutosize
        ref={textareaRef} // Okay
      />
  }
</div>

Playground Link

  • Related