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:
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>
)
}
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
/>
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>