diff --git a/app/javascript/packages/document-capture/components/acuant-capture.jsx b/app/javascript/packages/document-capture/components/acuant-capture.jsx index 4914b3a4357..0e68896c584 100644 --- a/app/javascript/packages/document-capture/components/acuant-capture.jsx +++ b/app/javascript/packages/document-capture/components/acuant-capture.jsx @@ -8,6 +8,7 @@ import { useImperativeHandle, } from 'react'; import { useI18n } from '@18f/identity-react-i18n'; +import { useIfStillMounted, useDidUpdateEffect } from '@18f/identity-react-hooks'; import AnalyticsContext from '../context/analytics'; import AcuantContext from '../context/acuant'; import FailedCaptureAttemptsContext from '../context/failed-capture-attempts'; @@ -18,8 +19,6 @@ import FullScreen from './full-screen'; import Button from './button'; import DeviceContext from '../context/device'; import UploadContext from '../context/upload'; -import useIfStillMounted from '../hooks/use-if-still-mounted'; -import useDidUpdateEffect from '../hooks/use-did-update-effect'; import useCounter from '../hooks/use-counter'; import useCookie from '../hooks/use-cookie'; diff --git a/app/javascript/packages/document-capture/components/capture-troubleshooting.jsx b/app/javascript/packages/document-capture/components/capture-troubleshooting.jsx index ea25445c511..c0eb2aded7d 100644 --- a/app/javascript/packages/document-capture/components/capture-troubleshooting.jsx +++ b/app/javascript/packages/document-capture/components/capture-troubleshooting.jsx @@ -1,10 +1,10 @@ import { useContext, useState } from 'react'; +import { useDidUpdateEffect } from '@18f/identity-react-hooks'; import FailedCaptureAttemptsContext from '../context/failed-capture-attempts'; import AnalyticsContext from '../context/analytics'; import CallbackOnMount from './callback-on-mount'; import CaptureAdvice from './capture-advice'; import { FormStepsContext } from './form-steps'; -import useDidUpdateEffect from '../hooks/use-did-update-effect'; /** @typedef {import('react').ReactNode} ReactNode */ diff --git a/app/javascript/packages/document-capture/components/file-image.jsx b/app/javascript/packages/document-capture/components/file-image.jsx index d4f9db5ac20..f7ceb43618f 100644 --- a/app/javascript/packages/document-capture/components/file-image.jsx +++ b/app/javascript/packages/document-capture/components/file-image.jsx @@ -1,5 +1,5 @@ import { useContext, useState, useEffect } from 'react'; -import useIfStillMounted from '../hooks/use-if-still-mounted'; +import { useIfStillMounted } from '@18f/identity-react-hooks'; import FileBase64CacheContext from '../context/file-base64-cache'; /** diff --git a/app/javascript/packages/document-capture/components/form-steps.jsx b/app/javascript/packages/document-capture/components/form-steps.jsx index 835842fec25..3b3a3b65820 100644 --- a/app/javascript/packages/document-capture/components/form-steps.jsx +++ b/app/javascript/packages/document-capture/components/form-steps.jsx @@ -1,13 +1,12 @@ import { useEffect, useRef, useState, createContext, useContext } from 'react'; import { useI18n } from '@18f/identity-react-i18n'; import { Alert } from '@18f/identity-components'; +import { useDidUpdateEffect, useIfStillMounted } from '@18f/identity-react-hooks'; import Button from './button'; import FormErrorMessage, { RequiredValueMissingError } from './form-error-message'; import PromptOnNavigate from './prompt-on-navigate'; import useHistoryParam from '../hooks/use-history-param'; import useForceRender from '../hooks/use-force-render'; -import useDidUpdateEffect from '../hooks/use-did-update-effect'; -import useIfStillMounted from '../hooks/use-if-still-mounted'; /** * @typedef FormStepError diff --git a/app/javascript/packages/document-capture/components/full-screen.jsx b/app/javascript/packages/document-capture/components/full-screen.jsx index 19fc2563593..aa95d442f2a 100644 --- a/app/javascript/packages/document-capture/components/full-screen.jsx +++ b/app/javascript/packages/document-capture/components/full-screen.jsx @@ -1,11 +1,11 @@ import { forwardRef, useImperativeHandle, useRef, useEffect } from 'react'; import { createPortal } from 'react-dom'; import { useI18n } from '@18f/identity-react-i18n'; +import { useIfStillMounted } from '@18f/identity-react-hooks'; import useAsset from '../hooks/use-asset'; import useToggleBodyClassByPresence from '../hooks/use-toggle-body-class-by-presence'; import useImmutableCallback from '../hooks/use-immutable-callback'; import useFocusTrap from '../hooks/use-focus-trap'; -import useIfStillMounted from '../hooks/use-if-still-mounted'; /** @typedef {import('focus-trap').FocusTrap} FocusTrap */ /** @typedef {import('react').ReactNode} ReactNode */ diff --git a/app/javascript/packages/document-capture/components/review-issues-step.jsx b/app/javascript/packages/document-capture/components/review-issues-step.jsx index d8571e0cfe5..3968de69a42 100644 --- a/app/javascript/packages/document-capture/components/review-issues-step.jsx +++ b/app/javascript/packages/document-capture/components/review-issues-step.jsx @@ -1,6 +1,7 @@ import { useContext, useState } from 'react'; import { hasMediaAccess } from '@18f/identity-device'; import { useI18n } from '@18f/identity-react-i18n'; +import { useDidUpdateEffect } from '@18f/identity-react-hooks'; import { FormStepsContext, FormStepsContinueButton } from './form-steps'; import DeviceContext from '../context/device'; import DocumentSideAcuantCapture from './document-side-acuant-capture'; @@ -14,7 +15,6 @@ import PageHeading from './page-heading'; import StartOverOrCancel from './start-over-or-cancel'; import Warning from './warning'; import AnalyticsContext from '../context/analytics'; -import useDidUpdateEffect from '../hooks/use-did-update-effect'; /** * @typedef {'front'|'back'} DocumentSide diff --git a/app/javascript/packages/document-capture/components/selfie-capture.jsx b/app/javascript/packages/document-capture/components/selfie-capture.jsx index 7ab49404bb3..89d581786b9 100644 --- a/app/javascript/packages/document-capture/components/selfie-capture.jsx +++ b/app/javascript/packages/document-capture/components/selfie-capture.jsx @@ -10,8 +10,8 @@ import { } from 'react'; import { Icon } from '@18f/identity-components'; import { useI18n } from '@18f/identity-react-i18n'; +import { useIfStillMounted } from '@18f/identity-react-hooks'; import FileImage from './file-image'; -import useIfStillMounted from '../hooks/use-if-still-mounted'; import useInstanceId from '../hooks/use-instance-id'; import useFocusFallbackRef from '../hooks/use-focus-fallback-ref'; import AppContext from '../context/app'; diff --git a/app/javascript/packages/react-hooks/README.md b/app/javascript/packages/react-hooks/README.md new file mode 100644 index 00000000000..9e9baa458b1 --- /dev/null +++ b/app/javascript/packages/react-hooks/README.md @@ -0,0 +1,3 @@ +# `@18f/identity-react-hooks` + +A collection of general-purpose [React hooks](https://reactjs.org/docs/hooks-intro.html). diff --git a/app/javascript/packages/react-hooks/index.ts b/app/javascript/packages/react-hooks/index.ts new file mode 100644 index 00000000000..92a82a73d2a --- /dev/null +++ b/app/javascript/packages/react-hooks/index.ts @@ -0,0 +1,2 @@ +export { default as useDidUpdateEffect } from './use-did-update-effect'; +export { default as useIfStillMounted } from './use-if-still-mounted'; diff --git a/app/javascript/packages/react-hooks/package.json b/app/javascript/packages/react-hooks/package.json new file mode 100644 index 00000000000..0c7f4ca36a9 --- /dev/null +++ b/app/javascript/packages/react-hooks/package.json @@ -0,0 +1,8 @@ +{ + "name": "@18f/identity-react-hooks", + "private": true, + "version": "1.0.0", + "peerDependencies": { + "react": ">=16.8.0" + } +} diff --git a/spec/javascripts/packages/document-capture/hooks/use-did-update-effect-spec.jsx b/app/javascript/packages/react-hooks/use-did-update-effect.spec.tsx similarity index 94% rename from spec/javascripts/packages/document-capture/hooks/use-did-update-effect-spec.jsx rename to app/javascript/packages/react-hooks/use-did-update-effect.spec.tsx index 84bd41e05bb..060c8f0b496 100644 --- a/spec/javascripts/packages/document-capture/hooks/use-did-update-effect-spec.jsx +++ b/app/javascript/packages/react-hooks/use-did-update-effect.spec.tsx @@ -1,6 +1,6 @@ import sinon from 'sinon'; import { renderHook } from '@testing-library/react-hooks'; -import useDidUpdateEffect from '@18f/identity-document-capture/hooks/use-did-update-effect'; +import useDidUpdateEffect from './use-did-update-effect'; describe('document-capture/hooks/use-did-update-effect', () => { context('no dependencies', () => { diff --git a/app/javascript/packages/document-capture/hooks/use-did-update-effect.js b/app/javascript/packages/react-hooks/use-did-update-effect.ts similarity index 86% rename from app/javascript/packages/document-capture/hooks/use-did-update-effect.js rename to app/javascript/packages/react-hooks/use-did-update-effect.ts index 1cffffd2eca..4db18b41615 100644 --- a/app/javascript/packages/document-capture/hooks/use-did-update-effect.js +++ b/app/javascript/packages/react-hooks/use-did-update-effect.ts @@ -4,10 +4,8 @@ import { useRef, useEffect } from 'react'; * A hook behaving the same as useEffect in invoking the given callback when dependencies change, * but does not call the callback during initial mount or when unmounting. It can be considered as * similar to ReactComponent#componentDidUpdate. - * - * @type {typeof useEffect} */ -function useDidUpdateEffect(callback, deps) { +const useDidUpdateEffect: typeof useEffect = (callback, deps) => { const isMounting = useRef(true); useEffect(() => { @@ -17,6 +15,6 @@ function useDidUpdateEffect(callback, deps) { callback(); } }, deps); -} +}; export default useDidUpdateEffect; diff --git a/spec/javascripts/packages/document-capture/hooks/use-if-still-mounted-spec.jsx b/app/javascript/packages/react-hooks/use-if-still-mounted.spec.tsx similarity index 89% rename from spec/javascripts/packages/document-capture/hooks/use-if-still-mounted-spec.jsx rename to app/javascript/packages/react-hooks/use-if-still-mounted.spec.tsx index 0b4b1666856..5be4bb40837 100644 --- a/spec/javascripts/packages/document-capture/hooks/use-if-still-mounted-spec.jsx +++ b/app/javascript/packages/react-hooks/use-if-still-mounted.spec.tsx @@ -1,6 +1,6 @@ import sinon from 'sinon'; import { renderHook } from '@testing-library/react-hooks'; -import useIfStillMounted from '@18f/identity-document-capture/hooks/use-if-still-mounted'; +import useIfStillMounted from './use-if-still-mounted'; describe('document-capture/hooks/use-if-still-mounted', () => { it('returns function which executes callback if component is still mounted', () => { diff --git a/app/javascript/packages/document-capture/hooks/use-if-still-mounted.js b/app/javascript/packages/react-hooks/use-if-still-mounted.ts similarity index 77% rename from app/javascript/packages/document-capture/hooks/use-if-still-mounted.js rename to app/javascript/packages/react-hooks/use-if-still-mounted.ts index 7b6f66a6084..df4579fac22 100644 --- a/app/javascript/packages/document-capture/hooks/use-if-still-mounted.js +++ b/app/javascript/packages/react-hooks/use-if-still-mounted.ts @@ -19,18 +19,12 @@ function useIfStillMounted() { }; }); - /** - * @template {(...args) => any} T - * @param {T} fn - */ - const ifStillMounted = (fn) => - /** @type {T} */ ( - (...args) => { - if (isMounted.current) { - fn(...args); - } + const ifStillMounted = any>(fn: T) => + ((...args) => { + if (isMounted.current) { + fn(...args); } - ); + }) as T; return ifStillMounted; }