Portal sample code of React TypeScript Cheatsheet seems broken

Viewed 12

I'm using a pretty strict version of TS and ESLint.

I ripped this portal from the docs here: https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/portals/

I modified the code to the below:

import React, { useEffect, useRef, ReactNode } from 'react'
import { createPortal } from 'react-dom'

interface Props {
  children?: ReactNode
  portalId: string
}

export const Portal: React.FC<Props> = ({ children, portalId }) => {
  const el = useRef(document.createElement('div'))

  useEffect(() => {
    const portalRoot = document.querySelector(`#${portalId}`) as HTMLElement

    const current = el.current

    portalRoot.appendChild(current)
    return () => void portalRoot?.removeChild(current) // error thrown here?
  }, [portalId])

  return createPortal(children, el.current)
}

Error below:

Expected 'undefined' and instead saw 'void'

When I remove the void (can't use undefined, otherwise rest of code is unreachable), as such:

import React, { useEffect, useRef, ReactNode } from 'react'
import { createPortal } from 'react-dom'

interface Props {
  children?: ReactNode
  portalId: string
}

export const Portal: React.FC<Props> = ({ children, portalId }) => {
  const el = useRef(document.createElement('div'))

  useEffect(() => {
    const portalRoot = document.querySelector(`#${portalId}`) as HTMLElement

    const current = el.current

    portalRoot.appendChild(current)
    return () => portalRoot?.removeChild(current) // how to fix?
  }, [portalId])

  return createPortal(children, el.current)
}

This results in another error:

Argument of type '() => () => HTMLDivElement' is not assignable to parameter of type 'EffectCallback'.
  Type '() => HTMLDivElement' is not assignable to type 'void | Destructor'.
    Type '() => HTMLDivElement' is not assignable to type 'Destructor'.
      Type 'HTMLDivElement' is not assignable to type 'void | { [UNDEFINED_VOID_ONLY]: never; }'

How to fix?

1 Answers

It just means that the "Destructor" (i.e. the clean-up function that you return from the useEffect) should return nothing.

But since you use an arrow function short form, its result is automatically returned. Simply wrap it with curly braces for example to prevent the automatic return:

return () => {
  portalRoot?.removeChild(current)
}
Related