How to correctly replace type any when returning children or map of children if array

Viewed 24969

I have a React function component that checks if children is an array. If it is not an array then it returns it. Otherwise it maps over the children and returns some JSX.

import React from 'react'

interface Props {
  children: React.ReactNode
}

const LineBreak: React.FC<Props> = ({ children }): any => {
  if (!Array.isArray(children)) return children
  return (
    <>
      {children.map((child, i, arr) => {
        if (i + 1 === arr.length) return child
        return <>{child}<br /></>
      })}
    </>
  )
}

export default LineBreak

What I would like to do is replace the any on line 7. I logically think that changing it to React.ReactNode would suffice, but that throws the type error:

Type '({ children }: PropsWithChildren<Props>) => ReactNode' is not assignable to type 'FC<Props>'.
  Type 'ReactNode' is not assignable to type 'ReactElement<any, any>'.
    Type 'string' is not assignable to type 'ReactElement<any, any>'.ts(2322)

I could really use some pointers on how to properly read these error messages.

I also tried to bypass this error message by changing the return type to string|React.ReactNode and expected the same error because from my limited typescript knowledge React.ReactNode includes the type string.

3 Answers

I was able to resolve this with two different methods.

  1. Remove React.FC type from the function.
import React from 'react'

interface Props {
  children: React.ReactNode
}

const LineBreak = ({children}: Props): React.ReactNode => {
  if (!Array.isArray(children)) return children
  return (
    <>
      {children.map((child, i, arr) => {
        if (i + 1 === arr.length) return child
        return <>{child}<br /></>
      })}
    </>
  )
}

export default LineBreak
  1. Wrap the single child return inside of a Fragment.
import React from 'react'

interface Props {
  children: React.ReactNode
}

const LineBreak: React.FC<Props> = ({children}): JSX.Element => {
  if (!Array.isArray(children)) return <>{children}</>
  return (
    <>
      {children.map((child, i, arr) => {
        if (i + 1 === arr.length) return child
        return <>{child}<br /></>
      })}
    </>
  )
}

export default LineBreak

From what I understand now, a React.FC can only be a return type of JSX.Element or equivalent. While children may be of type string, boolean, {} that does not conform to the JSX.Element type. So if I want to control the return type then I have to remove the React.FC, or if I want to use the React.FC, then I need to wrap the return of children inside of a fragment so that it correctly returns the various children types as a valid JSX.Element type.

The solutions from @amaster are good. Just adding my understanding of the error messages

I think this is what the error means

The error is saying that the expected return type for FC<Props> is ReactElement<any, any>

So Type '({ children }: PropsWithChildren<Props>) => ReactNode' is not assignable to type 'FC<Props>'. Means something with a return type of ReactNode doesn't match the expected return type of FC<Props>

And Type 'ReactNode' is not assignable to type 'ReactElement<any, any>'. Is saying you are trying to assign ReactNode as return type instead of the expected ReactElement<any, any>

The fix is to return ReactElement for FC. However you must also set children too.

import React from 'react'

interface Props {
  children: React.ReactElement
}

const LineBreak: React.FC<Props> = ({ children }): React.ReactElement => {
  if (!Array.isArray(children)) return children
  return (
    <>
      {children.map((child, i, arr) => {
        if (i + 1 === arr.length) return child
        return (
          <>
            {child}
            <br />
          </>
        )
      })}
    </>
  )
}

export default LineBreak
Related