focus-trap / focus-trap-react

A React component that traps focus
http://focus-trap.github.io/focus-trap-react/demo/
MIT License
713 stars 64 forks source link

'FocusTrap' cannot be used as a JSX component. #1036

Closed Vintotan closed 1 year ago

Vintotan commented 1 year ago

Thanks for using focus-trap-react! How can we help?

Settings:

I get this error message:

'FocusTrap' cannot be used as a JSX component.
Its instance type 'FocusTrap' is not a valid JSX element.
The types returned by 'render()' are incompatible between these types.

This is my modal.tsx component where I implemented focus-trap-react:

'use client';

import {
  Dispatch,
  ReactNode,
  SetStateAction,
  useCallback,
  useEffect,
  useRef,
} from 'react';
import FocusTrap from 'focus-trap-react';
import { AnimatePresence, motion } from 'framer-motion';
import Leaflet from '@ui/leaflet';
import useWindowSize from '@/hooks/use-window-size';

export default function Modal({
  children,
  showModal,
  setShowModal,
}: {
  children: ReactNode;
  showModal: boolean;
  setShowModal: Dispatch<SetStateAction<boolean>>;
}) {
  const desktopModalRef = useRef(null);

  const onKeyDown = useCallback(
    (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        setShowModal(false);
      }
    },
    [setShowModal],
  );

  useEffect(() => {
    document.addEventListener('keydown', onKeyDown);
    return () => document.removeEventListener('keydown', onKeyDown);
  }, [onKeyDown]);

  const { isMobile, isDesktop } = useWindowSize();

  return (
    <AnimatePresence>
      {showModal && (
        <>
          {isMobile && <Leaflet setShow={setShowModal}>{children}</Leaflet>}
          {isDesktop && (
            <>
              <FocusTrap focusTrapOptions={{ initialFocus: false }}>
                <motion.div
                  ref={desktopModalRef}
                  key="desktop-modal"
                  className="fixed inset-0 z-50 hidden min-h-screen items-center justify-center md:flex"
                  initial={{ scale: 0.8 }}
                  animate={{ scale: 0.9 }}
                  exit={{ scale: 0.2 }}
                  onMouseDown={(e) => {
                    if (desktopModalRef.current === e.target) {
                      setShowModal(false);
                    }
                  }}
                >
                  {children}
                </motion.div>
              </FocusTrap>
              <motion.div
                key="desktop-backdrop"
                className="bg-backgound fixed inset-0 z-30 backdrop-blur-md"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                onClick={() => setShowModal(false)}
              />
            </>
          )}
        </>
      )}
    </AnimatePresence>
  );
}
stefcameron commented 1 year ago

This is a very strange error. It sounds similar to one where you mistakenly import the wrong thing and end-up importing undefined, and then try to render that as a React component.

Typings for FocusTrap as well as the implementation itself hasn't changed much in a long time, so I tend to think it's not actually Focus-trap-react at issue, but something else.

I did notice what looks like a typo in one of your import statements:

import useWindowSize from '@/hooks/use-window-size';

Should that be @hooks instead of @/hooks?

In your code, you then call useWindowSize() to get isDesktop and use that as a condition to render <FocusTrap>, so there's a relationship there. But what is useWindowSize? Is it actually defined if that is, indeed, a typo?

Vintotan commented 1 year ago

It works now after updating TypeScript to the latest version. So my issue is fixed ;)

import useWindowSize from '@/hooks/use-window-size'; is correct.

This is my use-windows-size.ts :

import { useEffect, useState } from 'react';

export default function useWindowSize() {
  const [windowSize, setWindowSize] = useState<{
    width: number | undefined;
    height: number | undefined;
  }>({
    width: undefined,
    height: undefined,
  });

  useEffect(() => {
    // Handler to call on window resize
    function handleResize() {
      // Set window width/height to state
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }

    // Add event listener
    window.addEventListener('resize', handleResize);

    // Call handler right away so state gets updated with initial window size
    handleResize();

    // Remove event listener on cleanup
    return () => window.removeEventListener('resize', handleResize);
  }, []); // Empty array ensures that effect is only run on mount

  return {
    windowSize,
    isMobile: typeof windowSize?.width === 'number' && windowSize?.width < 768,
    isDesktop:
      typeof windowSize?.width === 'number' && windowSize?.width >= 768,
  };
}