diff --git a/package-lock.json b/package-lock.json index dc82fa1342..5b460b88ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35064,13 +35064,16 @@ } }, "node_modules/react-error-boundary": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", - "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-2.3.2.tgz", + "integrity": "sha512-ZMzi7s4pj/6A/6i9RS4tG7g1PdF2Rgr4/7FTQ8sbKHex19uNji0j+xq0OS//c6TUgQRKoL6P51BNNNFmYpRMhw==", "dev": true, - "license": "MIT", "dependencies": { - "@babel/runtime": "^7.12.5" + "@babel/runtime": "^7.11.2" + }, + "engines": { + "node": ">=10", + "npm": ">=6" }, "peerDependencies": { "react": ">=16.13.1" @@ -45419,7 +45422,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", - "react-error-boundary": "^5.0.0", + "react-error-boundary": "^2.3.1", "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.3.1", "react-linkify": "^1.0.0-alpha", diff --git a/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx b/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx index 47b9b84eb5..82695a8189 100644 --- a/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx +++ b/services/web/frontend/js/features/history/components/diff-view/diff-view.tsx @@ -5,6 +5,7 @@ import { Diff, DocDiffResponse } from '../../services/types/doc' import { useHistoryContext } from '../../context/history-context' import { diffDoc } from '../../services/api' import { highlightsFromDiffResponse } from '../../utils/highlights-from-diff-response' +import { useErrorHandler } from 'react-error-boundary' import useAsync from '../../../../shared/hooks/use-async' import { useTranslation } from 'react-i18next' @@ -13,6 +14,7 @@ function DiffView() { const { isLoading, data, runAsync } = useAsync() const { t } = useTranslation() const { updateRange, selectedFile } = selection + const handleError = useErrorHandler() useEffect(() => { if (!updateRange || !selectedFile?.pathname || loadingFileDiffs) { @@ -30,9 +32,11 @@ function DiffView() { selectedFile.pathname, abortController.signal ) - ).finally(() => { - abortController = null - }) + ) + .catch(handleError) + .finally(() => { + abortController = null + }) // Abort an existing request before starting a new one or on unmount return () => { @@ -40,7 +44,14 @@ function DiffView() { abortController.abort() } } - }, [projectId, runAsync, updateRange, selectedFile, loadingFileDiffs]) + }, [ + projectId, + runAsync, + updateRange, + selectedFile, + loadingFileDiffs, + handleError, + ]) const diff = useMemo(() => { let diff: Diff | null diff --git a/services/web/frontend/js/features/history/context/history-context.tsx b/services/web/frontend/js/features/history/context/history-context.tsx index 3740990941..7dc2c996c5 100644 --- a/services/web/frontend/js/features/history/context/history-context.tsx +++ b/services/web/frontend/js/features/history/context/history-context.tsx @@ -25,6 +25,7 @@ import { Update, } from '../services/types/update' import { Selection } from '../services/types/selection' +import { useErrorHandler } from 'react-error-boundary' import { getUpdateForVersion } from '../utils/history-details' import { getHueForUserId } from '@/shared/utils/colors' @@ -98,6 +99,7 @@ function useHistory() { ) const updatesAbortControllerRef = useRef(null) + const handleError = useErrorHandler() const fetchNextBatchOfUpdates = useCallback(() => { // If there is an in-flight request for updates, just let it complete, by @@ -197,10 +199,11 @@ function useHistory() { loadingState: 'ready', }) }) + .catch(handleError) .finally(() => { updatesAbortControllerRef.current = null }) - }, [updatesInfo, projectId, labels, userHasFullFeature]) + }, [updatesInfo, projectId, labels, handleError, userHasFullFeature]) // Abort in-flight updates request on unmount useEffect(() => { @@ -281,6 +284,7 @@ function useHistory() { } }) }) + .catch(handleError) .finally(() => { setLoadingFileDiffs(false) abortController = null @@ -291,7 +295,7 @@ function useHistory() { abortController.abort() } } - }, [projectId, fromV, toV, updateForToV]) + }, [projectId, fromV, toV, updateForToV, handleError]) useEffect(() => { // Set update range if there isn't one and updates have loaded diff --git a/services/web/frontend/js/features/history/context/hooks/use-restore-deleted-file.ts b/services/web/frontend/js/features/history/context/hooks/use-restore-deleted-file.ts index 2b8b740b51..226991e3b1 100644 --- a/services/web/frontend/js/features/history/context/hooks/use-restore-deleted-file.ts +++ b/services/web/frontend/js/features/history/context/hooks/use-restore-deleted-file.ts @@ -4,6 +4,7 @@ import { restoreFile } from '../../services/api' import { isFileRemoved } from '../../utils/file-diff' import { useHistoryContext } from '../history-context' import type { HistoryContextValue } from '../types/history-context-value' +import { useErrorHandler } from 'react-error-boundary' import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { findInTree } from '@/features/file-tree/util/find-in-tree' import { useCallback, useEffect, useState } from 'react' @@ -22,6 +23,7 @@ export function useRestoreDeletedFile() { const { projectId } = useHistoryContext() const { setView } = useLayoutContext() const { openDocWithId, openFileWithId } = useEditorManagerContext() + const handleError = useErrorHandler() const { fileTreeData } = useFileTreeData() const [state, setState] = useState('idle') const [restoredFileMetadata, setRestoredFileMetadata] = @@ -57,14 +59,14 @@ export function useRestoreDeletedFile() { if (state === 'waitingForFileTree') { const timer = window.setTimeout(() => { setState('timedOut') - throw new Error('timed out') + handleError(new Error('timed out')) }, 3000) return () => { window.clearTimeout(timer) } } - }, [state]) + }, [handleError, state]) const restoreDeletedFile = useCallback( (selection: HistoryContextValue['selection']) => { @@ -91,13 +93,13 @@ export function useRestoreDeletedFile() { }, error => { setState('error') - throw error + handleError(error) } ) } } }, - [projectId] + [handleError, projectId] ) return { restoreDeletedFile, isLoading } diff --git a/services/web/frontend/js/features/history/context/hooks/use-restore-project.ts b/services/web/frontend/js/features/history/context/hooks/use-restore-project.ts index 8675c1701d..ec4e4a4ef8 100644 --- a/services/web/frontend/js/features/history/context/hooks/use-restore-project.ts +++ b/services/web/frontend/js/features/history/context/hooks/use-restore-project.ts @@ -1,10 +1,12 @@ import { useCallback, useState } from 'react' +import { useErrorHandler } from 'react-error-boundary' import { restoreProjectToVersion } from '../../services/api' import { useLayoutContext } from '@/shared/context/layout-context' type RestorationState = 'initial' | 'restoring' | 'restored' | 'error' export const useRestoreProject = () => { + const handleError = useErrorHandler() const { setView } = useLayoutContext() const [restorationState, setRestorationState] = @@ -20,10 +22,10 @@ export const useRestoreProject = () => { }) .catch(err => { setRestorationState('error') - throw err + handleError(err) }) }, - [setView] + [handleError, setView] ) return { diff --git a/services/web/frontend/js/features/history/context/hooks/use-restore-selected-file.ts b/services/web/frontend/js/features/history/context/hooks/use-restore-selected-file.ts index f4d5b1b9ab..12b72faafd 100644 --- a/services/web/frontend/js/features/history/context/hooks/use-restore-selected-file.ts +++ b/services/web/frontend/js/features/history/context/hooks/use-restore-selected-file.ts @@ -3,6 +3,7 @@ import { restoreFileToVersion } from '../../services/api' import { isFileRemoved } from '../../utils/file-diff' import { useHistoryContext } from '../history-context' import type { HistoryContextValue } from '../types/history-context-value' +import { useErrorHandler } from 'react-error-boundary' import { useFileTreeData } from '@/shared/context/file-tree-data-context' import { findInTree } from '@/features/file-tree/util/find-in-tree' import { useCallback, useEffect, useState } from 'react' @@ -23,6 +24,7 @@ export function useRestoreSelectedFile() { const { projectId } = useHistoryContext() const { setView } = useLayoutContext() const { openDocWithId, openFileWithId } = useEditorManagerContext() + const handleError = useErrorHandler() const { fileTreeData } = useFileTreeData() const [state, setState] = useState('idle') const [restoredFileMetadata, setRestoredFileMetadata] = @@ -58,14 +60,14 @@ export function useRestoreSelectedFile() { if (state === 'waitingForFileTree') { const timer = window.setTimeout(() => { setState('timedOut') - throw new Error('timed out') + handleError(new Error('timed out')) }, RESTORE_FILE_TIMEOUT) return () => { window.clearTimeout(timer) } } - }, [state]) + }, [handleError, state]) const restoreSelectedFile = useCallback( (selection: HistoryContextValue['selection']) => { @@ -89,13 +91,13 @@ export function useRestoreSelectedFile() { }, error => { setState('error') - throw error + handleError(error) } ) } } }, - [projectId] + [handleError, projectId] ) return { restoreSelectedFile, isLoading } diff --git a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts index 86f47ec2d9..598cd60f15 100644 --- a/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts +++ b/services/web/frontend/js/features/source-editor/hooks/use-codemirror-scope.ts @@ -33,6 +33,7 @@ import { setSpellCheckLanguage } from '../extensions/spelling' import { setKeybindings } from '../extensions/keybindings' import { Highlight } from '../../../../../types/highlight' import { EditorView } from '@codemirror/view' +import { useErrorHandler } from 'react-error-boundary' import { setVisual } from '../extensions/visual/visual' import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' import { useUserSettingsContext } from '@/shared/context/user-settings-context' @@ -264,6 +265,8 @@ function useCodeMirrorScope(view: EditorView) { visual: showVisual, }) + const handleError = useErrorHandler() + const handleException = useCallback((exception: any) => { captureException(exception, { tags: { @@ -301,6 +304,7 @@ function useCodeMirrorScope(view: EditorView) { spelling: spellingRef.current, visual: visualRef.current, projectFeatures: projectFeaturesRef.current, + handleError, handleException, }), }) @@ -331,7 +335,7 @@ function useCodeMirrorScope(view: EditorView) { } // IMPORTANT: This effect must not depend on anything variable apart from currentDocument, // as the editor state is recreated when the effect runs. - }, [view, currentDocument, handleException]) + }, [view, currentDocument, handleError, handleException]) useEffect(() => { if (openDocName) { diff --git a/services/web/package.json b/services/web/package.json index 806bbe2f5d..a80b86dd0f 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -333,7 +333,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^17.0.2", - "react-error-boundary": "^5.0.0", + "react-error-boundary": "^2.3.1", "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.3.1", "react-linkify": "^1.0.0-alpha",