theKashey / react-focus-lock

It is a trap! A lock for a Focus. 🔓
MIT License
1.27k stars 67 forks source link

Invalid hook call when using it inside a library #256

Closed leticiafontoura closed 1 year ago

leticiafontoura commented 1 year ago

Hello!

The project I work on has its own npm package with our design system's components.

We use FocusLock on two different components but for some reason, when we import these components into the project, we get this error on screen:

image

the error only occurs when we use npm link to test the lib local

theKashey commented 1 year ago

Sorry, image with generic stack trace is not debuggable.

leticiafontoura commented 1 year ago

I understand, not sure this will be helpful but here is the full log from console:

react.development.js:1279 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (react.development.js:1279:1)
    at useEffect (react.development.js:1317:1)
    at x (index.js:142:1)
    at renderWithHooks (react-dom.development.js:12871:1)
    at mountIndeterminateComponent (react-dom.development.js:15201:1)
    at beginWork (react-dom.development.js:16138:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:168:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:212:1)
    at invokeGuardedCallback (react-dom.development.js:260:1)
    at beginWork$1 (react-dom.development.js:20041:1)
resolveDispatcher @ react.development.js:1279
useEffect @ react.development.js:1317
x @ index.js:142
renderWithHooks @ react-dom.development.js:12871
mountIndeterminateComponent @ react-dom.development.js:15201
beginWork @ react-dom.development.js:16138
callCallback @ react-dom.development.js:168
invokeGuardedCallbackDev @ react-dom.development.js:212
invokeGuardedCallback @ react-dom.development.js:260
beginWork$1 @ react-dom.development.js:20041
performUnitOfWork @ react-dom.development.js:19162
workLoopSync @ react-dom.development.js:19141
performSyncWorkOnRoot @ react-dom.development.js:18811
(anonymous) @ react-dom.development.js:9782
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushSyncCallbackQueueImpl @ react-dom.development.js:9778
flushSyncCallbackQueue @ react-dom.development.js:9768
flushPassiveEffectsImpl @ react-dom.development.js:19774
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushPassiveEffects @ react-dom.development.js:19724
(anonymous) @ react-dom.development.js:19621
workLoop @ scheduler.development.js:526
flushWork @ scheduler.development.js:488
performWorkUntilDeadline @ scheduler.development.js:141
react-dom.development.js:16918 The above error occurred in the <x> component:
    in x (at ModalWarningIncompleteData.tsx:55)
    in ModalWarningPrenatal (at BottomBarAttendance.tsx:410)
    in div (at BottomBarAttendance.tsx:336)
    in BottomBarAttendance (created by Connect(BottomBarAttendance))
    in Connect(BottomBarAttendance) (at MedicalRecord.tsx:448)
    in div (at MedicalRecord.tsx:388)
    in MedicalRecord (created by Connect(MedicalRecord))
    in Connect(MedicalRecord) (created by Context.Consumer)
    in Route (at ProtectRoutes.js:15)
    in Protect (at routes/index.tsx:67)
    in Routes (at App.js:40)
    in UserStorage (at App.js:38)
    in Router (created by BrowserRouter)
    in BrowserRouter (at App.js:37)
    in Provider (at App.js:36)
    in div (at App.js:35)
    in App (at src/index.js:33)

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
overrideMethod @ console.js:213
logCapturedError @ react-dom.development.js:16918
logError @ react-dom.development.js:16949
update.callback @ react-dom.development.js:17899
callCallback @ react-dom.development.js:10958
commitUpdateQueue @ react-dom.development.js:10976
commitLifeCycles @ react-dom.development.js:17217
commitLayoutEffects @ react-dom.development.js:19710
callCallback @ react-dom.development.js:168
invokeGuardedCallbackDev @ react-dom.development.js:212
invokeGuardedCallback @ react-dom.development.js:260
commitRootImpl @ react-dom.development.js:19487
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
commitRoot @ react-dom.development.js:19351
finishSyncRender @ react-dom.development.js:18855
performSyncWorkOnRoot @ react-dom.development.js:18844
(anonymous) @ react-dom.development.js:9782
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushSyncCallbackQueueImpl @ react-dom.development.js:9778
flushSyncCallbackQueue @ react-dom.development.js:9768
flushPassiveEffectsImpl @ react-dom.development.js:19774
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushPassiveEffects @ react-dom.development.js:19724
(anonymous) @ react-dom.development.js:19621
workLoop @ scheduler.development.js:526
flushWork @ scheduler.development.js:488
performWorkUntilDeadline @ scheduler.development.js:141
Show 1 more frame
react.development.js:1279 Uncaught Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
    at resolveDispatcher (react.development.js:1279:1)
    at useEffect (react.development.js:1317:1)
    at x (index.js:142:1)
    at renderWithHooks (react-dom.development.js:12871:1)
    at mountIndeterminateComponent (react-dom.development.js:15201:1)
    at beginWork (react-dom.development.js:16138:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:168:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:212:1)
    at invokeGuardedCallback (react-dom.development.js:260:1)
    at beginWork$1 (react-dom.development.js:20041:1)
resolveDispatcher @ react.development.js:1279
useEffect @ react.development.js:1317
x @ index.js:142
renderWithHooks @ react-dom.development.js:12871
mountIndeterminateComponent @ react-dom.development.js:15201
beginWork @ react-dom.development.js:16138
callCallback @ react-dom.development.js:168
invokeGuardedCallbackDev @ react-dom.development.js:212
invokeGuardedCallback @ react-dom.development.js:260
beginWork$1 @ react-dom.development.js:20041
performUnitOfWork @ react-dom.development.js:19162
workLoopSync @ react-dom.development.js:19141
performSyncWorkOnRoot @ react-dom.development.js:18811
(anonymous) @ react-dom.development.js:9782
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushSyncCallbackQueueImpl @ react-dom.development.js:9778
flushSyncCallbackQueue @ react-dom.development.js:9768
flushPassiveEffectsImpl @ react-dom.development.js:19774
unstable_runWithPriority @ scheduler.development.js:571
runWithPriority$1 @ react-dom.development.js:9737
flushPassiveEffects @ react-dom.development.js:19724
(anonymous) @ react-dom.development.js:19621
workLoop @ scheduler.development.js:526
flushWork @ scheduler.development.js:488
performWorkUntilDeadline @ scheduler.development.js:141

the component from our lib

/* eslint-disable react/jsx-no-bind */
import React, { useEffect } from 'react'
import ReactDOM from 'react-dom'
import  FocusLock  from 'react-focus-lock'
import { textPrimary } from '../../colors'
import { IconButton } from '../IconButton/IconButton'
import './AccessibleModal.scss'

export interface Props {
  children: React.ReactNode;
  extraInnerClass?: string;
  extraOuterClassName?: string;
  headerText: string;
  showCloseButton?: boolean;
  visible: boolean;
  lastIdFocused?: string;
  maxWidthClass?: string;
  hasBackButton?: boolean;
  titleText?: string;
  extraClassModal?: string;
  onClose: () => void;
  onBackButton?: () => void;
}

export const AccessibleModal: React.FC<Props> = ({
  headerText,
  children,
  extraInnerClass,
  extraOuterClassName,
  maxWidthClass,
  showCloseButton,
  visible,
  lastIdFocused,
  hasBackButton,
  titleText,
  extraClassModal,
  onClose,
  onBackButton
}) => {
  function onCloseAndRefocused() {
    if (lastIdFocused !== undefined && lastIdFocused !== '') {
      if (document.getElementById(lastIdFocused)) {
        (document.getElementById(lastIdFocused) as any).focus()
      }
    }
    onClose()
  }

  function onKeyDown(event: KeyboardEvent) {
    if (event && event.key === 'Escape' && visible) {
      onCloseAndRefocused()
    }
  }

  useEffect(() => document.removeEventListener('keydown', onKeyDown, false))

  useEffect(() => {
    if (visible) {
      document.body.style.overflow = 'hidden'
      document.addEventListener('keydown', onKeyDown, false)
    } else {
      document.body.style.overflow = 'unset'
    }
  }, [visible])

  const modal = (
    <FocusLock className={extraOuterClassName}>
      <div
        className="modal-overlay"
        onClick={onCloseAndRefocused}
        aria-hidden="true"
        role="dialog"
      />
      <div
        role="dialog"
        className={`accessible-modal ${extraClassModal} ${maxWidthClass}`}
        aria-modal
        aria-labelledby={headerText}
        tabIndex={-1}
      >
      <div className={`modal__inner ${maxWidthClass} ${extraInnerClass}`}>
        <div className="modal-container">
          <span className={hasBackButton ? '' : 'placeholder-span'}>
            {hasBackButton && (
              <IconButton
                aria-label="Voltar"
                variant="subtle"
                width="32px"
                height="32px"
                iconType="icon-Property-2Arrow---Left"
                iconSize="20px"
                iconColor={textPrimary}
                data-dismiss="modal"
                onClick={onBackButton}
              />
            )}
          </span>
          {titleText && (
            <p className="text-fontSmall text-center">
              {titleText}
            </p>
          )}
          <span className={showCloseButton ? '' : 'placeholder-span'}>
            {showCloseButton && (
              <IconButton
                aria-label="Fechar"
                variant="subtle"
                width="32px"
                height="32px"
                iconType="icon-Property-2Close"
                iconSize="20px"
                iconColor={textPrimary}
                data-dismiss="modal"
                onClick={onCloseAndRefocused}
              />
            )}
          </span>
        </div>
        {children}
      </div>
    </div>
  </FocusLock>
  )
  return visible ? ReactDOM.createPortal(modal, document.body) : null
}

how we're using it in the project

  return (
    <AccessibleModal
      extraOuterClassName="medical-record-modal"
      extraInnerClass={visible ? 'active' : 'inactive'}
      visible={visible}
      onClose={suppressModal}
      headerText="Finalizar atendimento"
      lastIdFocused={lastIdFocused}
      showCloseButton
      maxWidthClass="prenatal"
    >
      <img
        src={MedicalRecordWarningIcon}
        alt=""
        className="medical-record-icon"
        width={89}
        height={89}
      />
      <div className="text-center flex flex-col">
        <h4 className="mt-4 mb-4 text-center text-textPrimary text-fontSmall">
          {modalText()}
        </h4>
        <ul className="text-left ml-2 text-fontDefault text-textSecondary">
          {maternalDesireError
            ? <li>- desejo materno de via de parto</li>
            : null}
          {allRequired && pregnancyNotesError
            ? <li>- modificações no campo de observações da gravidez</li>
            : null}
          {requiredComorbidity
            ? <li>- comorbidades pré gestacionais</li>
            : null}
          {requiredIntercurrence
            ? <li>- intercorrências da gestação</li>
            : null}
        </ul>
        <p className="text-center mb-10 text-fontDefault text-textSecondary">
          Por favor, atualize os dados antes de finalizar a consulta para manter a equipe informada!
        </p>
      </div>
      <div>
        <div className="row justify-around items-center">
          <div className="pl-0 pr-0">
            <RegularButton
              type="button"
              label="Voltar e atualizar"
              extraClass="flex-1"
              variant="text"
              onClick={suppressModal}
            />
          </div>
          {!allRequired && (
            <div className="pr-0">
              <RegularButton
                type="button"
                label="Finalizar e assinar"
                extraClass="flex-1"
                onClick={onFinish}
              />
            </div>
          )}
        </div>
      </div>
    </AccessibleModal>
  )

I can't give you a codesandbox for example because the error only happens locally when we connect the library into the project using npm link

theKashey commented 1 year ago

How moment related to the focus-lock:

npm link is a source of a problem - see https://github.com/facebook/react/issues/13991#issuecomment-463486871

There are multiple ways to resolve the issue, follow the linked issue.