deadcoder0904
deadcoder0904

Reputation: 8683

Converting a generic React Component to TypeScript throws error

I am trying to port https://github.com/catalinmiron/react-typical to TypeScript. However, I am facing some issues.

Here's the screenshot with errors in VSCode:

TS project in VSCode

Here's the same code for brevity:

import React from 'react'
import { type, type as loopedType } from '@camwiegert/typical'
import styles from './styles.module.css'

type Props = {
    steps: Array<any>
    loop: number
    className: string
    wrapper: React.Component
}

const Typical: React.FC<Props> = ({ steps, loop, className, wrapper = 'p' }) => {
    const typicalRef = React.useRef<HTMLElement>(null)
    const Component: string = wrapper
    const classNames: string[] = [styles.typicalWrapper]

    if (className) {
        classNames.unshift(className)
    }

    React.useEffect(() => {
        if (loop === Infinity) {
            type(typicalRef.current, ...steps, loopedType)
        } else if (typeof loop === 'number') {
            type(typicalRef.current, ...Array(loop).fill(steps).flat())
        } else {
            type(typicalRef.current, ...steps)
        }
    }, [typicalRef])

    return <Component ref={typicalRef} className={classNames.join(' ')} />
}

export default React.memo(Typical)

I am unable to write type for Component.

I tried doing the following too:

const Component = React.Component | string

But it says 'Component' refers to a value, but is being used as a type here. Did you mean 'typeof Component'? near return <Component .../> with underline over Component.

I am also unable to convert the typicalRef as typicalRef.current always throws error by showing red squiggly lines under it. Same thing with flat() as well as classNames.join(' ').

I am losing my brain over it. Can't seem to figure it out. Would love any pointers?

Upvotes: 0

Views: 598

Answers (3)

deadcoder0904
deadcoder0904

Reputation: 8683

I couldn't solve it using directly as I think TypeScript itself doesn't support it https://github.com/microsoft/TypeScript/issues/28892

But I did solve it using React.createElement syntax. My entire code looks like this right now:

import React from 'react'
import { type, type as loopedType } from '@camwiegert/typical'
import styles from './styles.module.css'

type Props = {
    steps: Array<any>
    loop: number
    className?: string
    wrapper: keyof JSX.IntrinsicElements
} & React.HTMLAttributes<HTMLOrSVGElement>

const Typical = ({ steps, loop, className, wrapper: Wrapper = 'p' }: Props) => {
    const typicalRef: React.RefObject<HTMLElement> = React.useRef<HTMLElement>(null)
    const classNames: string[] = [styles.typicalWrapper]

    if (className) {
        classNames.unshift(className)
    }

    const typicalStyles: string = classNames.join(' ')

    React.useEffect(() => {
        if (loop === Infinity) {
            type(typicalRef.current as HTMLElement, ...steps, loopedType)
        } else if (typeof loop === 'number') {
            type(typicalRef.current as HTMLElement, ...Array(loop).fill(steps).flat())
        } else {
            type(typicalRef.current as HTMLElement, ...steps)
        }
    }, [typicalRef])

    return React.createElement(Wrapper, {
        ref: typicalRef,
        className: typicalStyles,
    })
}

export default React.memo(Typical)

Upvotes: 2

tmhao2005
tmhao2005

Reputation: 17474

I think you have to set your wrapper to React.ComponentType<React.PropsWithRef<any>> which is accurate React type and rename your wrapper directly (do not re-assign in the body that tsc can be confused with mixed type as string) so your code might change as following:

type Props = {
 steps: Array<any>
 loop: number
 className: string
 wrapper: React.ComponentType<React.PropsWithRef<any>>
}

const Typical: React.FC<Props> = ({ steps, loop, className, wrapper: Component = 'p' }) => {
  const typicalRef = React.useRef<HTMLElement>()
  const classNames: string[] = [styles.typicalWrapper]

  if (className) {
    classNames.unshift(className)
  }

  React.useEffect(() => {
    if (loop === Infinity) {
        type(typicalRef.current, ...steps, loopedType)
    } else if (typeof loop === 'number') {
        type(typicalRef.current, ...Array(loop).fill(steps).flat())
    } else {
        type(typicalRef.current, ...steps)
    }
  }, [typicalRef]) 

  return <Component ref={typicalRef} className={classNames.join(' ')} />
}

Since flat method is only available from "es2019" which means you have to include it in the the tsc build by adding to the tsconfig.json following:

"compilerOptions": {
  "lib": ["ES2019"]
},

Upvotes: 0

Emre Koc
Emre Koc

Reputation: 1588

For the Component part, set it in the props instead of a new variable:

const Typical: React.FC<Props> = ({ wrapper:Component = 'p' }) => {
    return <Component />
}

Upvotes: 0

Related Questions