mirror of
https://github.com/actualbudget/actual.git
synced 2026-05-06 20:15:33 -05:00
* Addition of scoped ErrorBoundarys per 7391 * Adjusted to use FeatureErrorfallback from #7437
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import React from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
|
||||
import { ManageRules } from './ManageRules';
|
||||
import { Page } from './Page';
|
||||
|
||||
export function ManageRulesPage() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Page header={t('Rules')}>
|
||||
<ManageRules isModal={false} payeeId={null} />
|
||||
</Page>
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<Page header={t('Rules')}>
|
||||
<ManageRules isModal={false} payeeId={null} />
|
||||
</Page>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { createRef, PureComponent, useEffect, useMemo } from 'react';
|
||||
import type { ReactElement, RefObject } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { Navigate, useLocation, useParams } from 'react-router';
|
||||
|
||||
@@ -43,6 +44,7 @@ import {
|
||||
useUpdateAccountMutation,
|
||||
} from '#accounts';
|
||||
import { markAccountRead } from '#accounts/accountsSlice';
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
import type { SavedFilter } from '#components/filters/SavedFilterMenuButton';
|
||||
import { TransactionList } from '#components/transactions/TransactionList';
|
||||
import { validateAccountName } from '#components/util/accountValidation';
|
||||
@@ -2031,48 +2033,50 @@ export function Account() {
|
||||
createPayee.mutateAsync({ name });
|
||||
|
||||
return (
|
||||
<SchedulesProvider query={schedulesQuery}>
|
||||
<SplitsExpandedProvider
|
||||
initialMode={expandSplits ? 'collapse' : 'expand'}
|
||||
>
|
||||
<AccountHack
|
||||
newTransactions={newTransactions}
|
||||
matchedTransactions={matchedTransactions}
|
||||
accounts={accounts}
|
||||
failedAccounts={failedAccounts}
|
||||
dateFormat={dateFormat}
|
||||
hideFraction={String(hideFraction) === 'true'}
|
||||
expandSplits={expandSplits}
|
||||
showBalances={String(showBalances) === 'true'}
|
||||
setShowBalances={showBalances =>
|
||||
setShowBalances(String(showBalances))
|
||||
}
|
||||
showNetWorthChart={String(showNetWorthChart) === 'true'}
|
||||
setShowNetWorthChart={val => setShowNetWorthChart(String(val))}
|
||||
showCleared={String(hideCleared) !== 'true'}
|
||||
setShowCleared={val => setHideCleared(String(!val))}
|
||||
showReconciled={String(hideReconciled) !== 'true'}
|
||||
setShowReconciled={val => setHideReconciled(String(!val))}
|
||||
showExtraBalances={String(showExtraBalances) === 'true'}
|
||||
setShowExtraBalances={extraBalances =>
|
||||
setShowExtraBalances(String(extraBalances))
|
||||
}
|
||||
payees={payees}
|
||||
modalShowing={modalShowing}
|
||||
accountsSyncing={accountsSyncing}
|
||||
filterConditions={filterConditions}
|
||||
categoryGroups={categoryGroups}
|
||||
accountId={params.id}
|
||||
categoryId={location?.state?.categoryId}
|
||||
location={location}
|
||||
savedFilters={savedFiters}
|
||||
onReopenAccount={onReopenAccount}
|
||||
onUpdateAccount={onUpdateAccount}
|
||||
onUnlinkAccount={onUnlinkAccount}
|
||||
onSyncAndDownload={onSyncAndDownload}
|
||||
onCreatePayee={onCreatePayee}
|
||||
/>
|
||||
</SplitsExpandedProvider>
|
||||
</SchedulesProvider>
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<SchedulesProvider query={schedulesQuery}>
|
||||
<SplitsExpandedProvider
|
||||
initialMode={expandSplits ? 'collapse' : 'expand'}
|
||||
>
|
||||
<AccountHack
|
||||
newTransactions={newTransactions}
|
||||
matchedTransactions={matchedTransactions}
|
||||
accounts={accounts}
|
||||
failedAccounts={failedAccounts}
|
||||
dateFormat={dateFormat}
|
||||
hideFraction={String(hideFraction) === 'true'}
|
||||
expandSplits={expandSplits}
|
||||
showBalances={String(showBalances) === 'true'}
|
||||
setShowBalances={showBalances =>
|
||||
setShowBalances(String(showBalances))
|
||||
}
|
||||
showNetWorthChart={String(showNetWorthChart) === 'true'}
|
||||
setShowNetWorthChart={val => setShowNetWorthChart(String(val))}
|
||||
showCleared={String(hideCleared) !== 'true'}
|
||||
setShowCleared={val => setHideCleared(String(!val))}
|
||||
showReconciled={String(hideReconciled) !== 'true'}
|
||||
setShowReconciled={val => setHideReconciled(String(!val))}
|
||||
showExtraBalances={String(showExtraBalances) === 'true'}
|
||||
setShowExtraBalances={extraBalances =>
|
||||
setShowExtraBalances(String(extraBalances))
|
||||
}
|
||||
payees={payees}
|
||||
modalShowing={modalShowing}
|
||||
accountsSyncing={accountsSyncing}
|
||||
filterConditions={filterConditions}
|
||||
categoryGroups={categoryGroups}
|
||||
accountId={params.id}
|
||||
categoryId={location?.state?.categoryId}
|
||||
location={location}
|
||||
savedFilters={savedFiters}
|
||||
onReopenAccount={onReopenAccount}
|
||||
onUpdateAccount={onUpdateAccount}
|
||||
onUnlinkAccount={onUnlinkAccount}
|
||||
onSyncAndDownload={onSyncAndDownload}
|
||||
onCreatePayee={onCreatePayee}
|
||||
/>
|
||||
</SplitsExpandedProvider>
|
||||
</SchedulesProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// @ts-strict-ignore
|
||||
import React, { useEffect } from 'react';
|
||||
import type { ComponentProps } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { AutoSizer } from 'react-virtualized-auto-sizer';
|
||||
|
||||
import { View } from '@actual-app/components/view';
|
||||
import * as monthUtils from '@actual-app/core/shared/months';
|
||||
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
import { useGlobalPref } from '#hooks/useGlobalPref';
|
||||
|
||||
import { useBudgetMonthCount } from './BudgetMonthCountContext';
|
||||
@@ -131,20 +133,22 @@ const DynamicBudgetTable = ({
|
||||
}}
|
||||
>
|
||||
<View style={{ width: '100%', maxWidth }}>
|
||||
<BudgetPageHeader
|
||||
startMonth={prewarmStartMonth}
|
||||
numMonths={numMonths}
|
||||
monthBounds={monthBounds}
|
||||
onMonthSelect={_onMonthSelect}
|
||||
/>
|
||||
<BudgetTable
|
||||
type={type}
|
||||
prewarmStartMonth={prewarmStartMonth}
|
||||
startMonth={startMonth}
|
||||
numMonths={numMonths}
|
||||
monthBounds={monthBounds}
|
||||
{...props}
|
||||
/>
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<BudgetPageHeader
|
||||
startMonth={prewarmStartMonth}
|
||||
numMonths={numMonths}
|
||||
monthBounds={monthBounds}
|
||||
onMonthSelect={_onMonthSelect}
|
||||
/>
|
||||
<BudgetTable
|
||||
type={type}
|
||||
prewarmStartMonth={prewarmStartMonth}
|
||||
startMonth={startMonth}
|
||||
numMonths={numMonths}
|
||||
monthBounds={monthBounds}
|
||||
{...props}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { Button } from '@actual-app/components/button';
|
||||
@@ -9,6 +10,7 @@ import { q } from '@actual-app/core/shared/query';
|
||||
import type { ScheduleEntity } from '@actual-app/core/types/models';
|
||||
|
||||
import { Search } from '#components/common/Search';
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
import { Page } from '#components/Page';
|
||||
import { useSchedules } from '#hooks/useSchedules';
|
||||
import { pushModal } from '#modals/modalsSlice';
|
||||
@@ -85,66 +87,68 @@ export function Schedules() {
|
||||
} = useSchedules({ query: schedulesQuery });
|
||||
|
||||
return (
|
||||
<Page header={t('Schedules')}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
padding: '0 0 15px',
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<Search
|
||||
placeholder={t('Filter schedules…')}
|
||||
value={filter}
|
||||
onChange={setFilter}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<SchedulesTable
|
||||
isLoading={isSchedulesLoading}
|
||||
schedules={schedules}
|
||||
filter={filter}
|
||||
statuses={statuses}
|
||||
allowCompleted
|
||||
onSelect={onEdit}
|
||||
onAction={onAction}
|
||||
style={{ backgroundColor: theme.tableBackground }}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
margin: '20px 0',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<Page header={t('Schedules')}>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: '1em',
|
||||
padding: '0 0 15px',
|
||||
}}
|
||||
>
|
||||
<Button onPress={onDiscover}>
|
||||
<Trans>Find schedules</Trans>
|
||||
</Button>
|
||||
<Button onPress={onChangeUpcomingLength}>
|
||||
<Trans>Change upcoming length</Trans>
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<Search
|
||||
placeholder={t('Filter schedules…')}
|
||||
value={filter}
|
||||
onChange={setFilter}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<SchedulesTable
|
||||
isLoading={isSchedulesLoading}
|
||||
schedules={schedules}
|
||||
filter={filter}
|
||||
statuses={statuses}
|
||||
allowCompleted
|
||||
onSelect={onEdit}
|
||||
onAction={onAction}
|
||||
style={{ backgroundColor: theme.tableBackground }}
|
||||
/>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
margin: '20px 0',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: '1em',
|
||||
}}
|
||||
>
|
||||
<Button onPress={onDiscover}>
|
||||
<Trans>Find schedules</Trans>
|
||||
</Button>
|
||||
<Button onPress={onChangeUpcomingLength}>
|
||||
<Trans>Change upcoming length</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
<Button variant="primary" onPress={onAdd}>
|
||||
<Trans>Add new schedule</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
<Button variant="primary" onPress={onAdd}>
|
||||
<Trans>Add new schedule</Trans>
|
||||
</Button>
|
||||
</View>
|
||||
</Page>
|
||||
</Page>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useResponsive } from '@actual-app/components/hooks/useResponsive';
|
||||
@@ -11,6 +12,7 @@ import * as Platform from '@actual-app/core/shared/platform';
|
||||
import { css } from '@emotion/css';
|
||||
import { Resizable } from 're-resizable';
|
||||
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
import { useGlobalPref } from '#hooks/useGlobalPref';
|
||||
import { useLocalPref } from '#hooks/useLocalPref';
|
||||
import { useResizeObserver } from '#hooks/useResizeObserver';
|
||||
@@ -67,69 +69,75 @@ export function Sidebar() {
|
||||
});
|
||||
|
||||
return (
|
||||
<Resizable
|
||||
defaultSize={{
|
||||
width: sidebarWidth,
|
||||
height: '100%',
|
||||
}}
|
||||
onResizeStop={onResizeStop}
|
||||
maxWidth={MAX_SIDEBAR_WIDTH}
|
||||
minWidth={MIN_SIDEBAR_WIDTH}
|
||||
enable={{
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}
|
||||
>
|
||||
<View
|
||||
innerRef={containerRef}
|
||||
className={css({
|
||||
color: theme.sidebarItemText,
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<Resizable
|
||||
defaultSize={{
|
||||
width: sidebarWidth,
|
||||
height: '100%',
|
||||
backgroundColor: theme.sidebarBackground,
|
||||
'& .float': {
|
||||
opacity: isFloating ? 1 : 0,
|
||||
transition: 'opacity .25s, width .25s',
|
||||
width: hasWindowButtons || isFloating ? null : 0,
|
||||
} as CSSProperties,
|
||||
'&:hover .float': {
|
||||
opacity: 1,
|
||||
width: hasWindowButtons ? null : 'auto',
|
||||
} as CSSProperties,
|
||||
flex: 1,
|
||||
...styles.darkScrollbar,
|
||||
})}
|
||||
}}
|
||||
onResizeStop={onResizeStop}
|
||||
maxWidth={MAX_SIDEBAR_WIDTH}
|
||||
minWidth={MIN_SIDEBAR_WIDTH}
|
||||
enable={{
|
||||
top: false,
|
||||
right: true,
|
||||
bottom: false,
|
||||
left: false,
|
||||
topRight: false,
|
||||
bottomRight: false,
|
||||
bottomLeft: false,
|
||||
topLeft: false,
|
||||
}}
|
||||
>
|
||||
<BudgetName>
|
||||
{!sidebar.alwaysFloats && (
|
||||
<ToggleButton isFloating={isFloating} onFloat={onFloat} />
|
||||
)}
|
||||
</BudgetName>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
'@media screen and (max-height: 480px)': {
|
||||
overflowY: 'auto',
|
||||
},
|
||||
}}
|
||||
innerRef={containerRef}
|
||||
className={css({
|
||||
color: theme.sidebarItemText,
|
||||
height: '100%',
|
||||
backgroundColor: theme.sidebarBackground,
|
||||
'& .float': {
|
||||
opacity: isFloating ? 1 : 0,
|
||||
transition: 'opacity .25s, width .25s',
|
||||
width: hasWindowButtons || isFloating ? null : 0,
|
||||
} as CSSProperties,
|
||||
'&:hover .float': {
|
||||
opacity: 1,
|
||||
width: hasWindowButtons ? null : 'auto',
|
||||
} as CSSProperties,
|
||||
flex: 1,
|
||||
...styles.darkScrollbar,
|
||||
})}
|
||||
>
|
||||
<PrimaryButtons />
|
||||
<BudgetName>
|
||||
{!sidebar.alwaysFloats && (
|
||||
<ToggleButton isFloating={isFloating} onFloat={onFloat} />
|
||||
)}
|
||||
</BudgetName>
|
||||
|
||||
<Accounts />
|
||||
<View
|
||||
style={{
|
||||
flexGrow: 1,
|
||||
'@media screen and (max-height: 480px)': {
|
||||
overflowY: 'auto',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<PrimaryButtons />
|
||||
|
||||
<SecondaryButtons
|
||||
buttons={[
|
||||
{ title: t('Add account'), Icon: SvgAdd, onClick: onAddAccount },
|
||||
]}
|
||||
/>
|
||||
<Accounts />
|
||||
|
||||
<SecondaryButtons
|
||||
buttons={[
|
||||
{
|
||||
title: t('Add account'),
|
||||
Icon: SvgAdd,
|
||||
onClick: onAddAccount,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Resizable>
|
||||
</Resizable>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// TODO: remove strict
|
||||
import { useCallback, useLayoutEffect, useRef } from 'react';
|
||||
import type { RefObject } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { theme } from '@actual-app/components/theme';
|
||||
@@ -29,6 +30,7 @@ import type {
|
||||
TransactionFilterEntity,
|
||||
} from '@actual-app/core/types/models';
|
||||
|
||||
import { FeatureErrorFallback } from '#components/FeatureErrorFallback';
|
||||
import type { TableHandleRef } from '#components/table';
|
||||
import { isValidBoundaryDrop } from '#hooks/useDragDrop';
|
||||
import type { DropPosition } from '#hooks/useDragDrop';
|
||||
@@ -722,53 +724,55 @@ export function TransactionList({
|
||||
);
|
||||
|
||||
return (
|
||||
<TransactionTable
|
||||
ref={tableRef}
|
||||
transactions={allTransactions}
|
||||
loadMoreTransactions={loadMoreTransactions}
|
||||
accounts={accounts}
|
||||
categoryGroups={categoryGroups}
|
||||
payees={payees}
|
||||
balances={balances}
|
||||
showBalances={showBalances}
|
||||
showReconciled={showReconciled}
|
||||
showCleared={showCleared}
|
||||
showAccount={showAccount}
|
||||
showCategory
|
||||
currentAccountId={account && account.id}
|
||||
currentCategoryId={category && category.id}
|
||||
isAdding={isAdding}
|
||||
isNew={isNew}
|
||||
isMatched={isMatched}
|
||||
dateFormat={dateFormat}
|
||||
hideFraction={hideFraction}
|
||||
renderEmpty={renderEmpty}
|
||||
onSave={onSave}
|
||||
onApplyRules={onApplyRules}
|
||||
onSplit={onSplit}
|
||||
onCloseAddTransaction={onCloseAddTransaction}
|
||||
onAdd={onAdd}
|
||||
onAddSplit={onAddSplit}
|
||||
onManagePayees={onManagePayees}
|
||||
onCreatePayee={onCreatePayee}
|
||||
style={{ backgroundColor: theme.tableBackground }}
|
||||
onNavigateToTransferAccount={onNavigateToTransferAccount}
|
||||
onNavigateToSchedule={onNavigateToSchedule}
|
||||
onNotesTagClick={onNotesTagClick}
|
||||
onSort={onSort}
|
||||
sortField={sortField}
|
||||
ascDesc={ascDesc}
|
||||
isFiltered={isFiltered}
|
||||
onReorder={allowReorder ? onReorder : undefined}
|
||||
onBatchDelete={onBatchDelete}
|
||||
onBatchDuplicate={onBatchDuplicate}
|
||||
onBatchLinkSchedule={onBatchLinkSchedule}
|
||||
onBatchUnlinkSchedule={onBatchUnlinkSchedule}
|
||||
onCreateRule={onCreateRule}
|
||||
onScheduleAction={onScheduleAction}
|
||||
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
|
||||
showSelection={showSelection}
|
||||
allowSplitTransaction={allowSplitTransaction}
|
||||
/>
|
||||
<ErrorBoundary FallbackComponent={FeatureErrorFallback}>
|
||||
<TransactionTable
|
||||
ref={tableRef}
|
||||
transactions={allTransactions}
|
||||
loadMoreTransactions={loadMoreTransactions}
|
||||
accounts={accounts}
|
||||
categoryGroups={categoryGroups}
|
||||
payees={payees}
|
||||
balances={balances}
|
||||
showBalances={showBalances}
|
||||
showReconciled={showReconciled}
|
||||
showCleared={showCleared}
|
||||
showAccount={showAccount}
|
||||
showCategory
|
||||
currentAccountId={account && account.id}
|
||||
currentCategoryId={category && category.id}
|
||||
isAdding={isAdding}
|
||||
isNew={isNew}
|
||||
isMatched={isMatched}
|
||||
dateFormat={dateFormat}
|
||||
hideFraction={hideFraction}
|
||||
renderEmpty={renderEmpty}
|
||||
onSave={onSave}
|
||||
onApplyRules={onApplyRules}
|
||||
onSplit={onSplit}
|
||||
onCloseAddTransaction={onCloseAddTransaction}
|
||||
onAdd={onAdd}
|
||||
onAddSplit={onAddSplit}
|
||||
onManagePayees={onManagePayees}
|
||||
onCreatePayee={onCreatePayee}
|
||||
style={{ backgroundColor: theme.tableBackground }}
|
||||
onNavigateToTransferAccount={onNavigateToTransferAccount}
|
||||
onNavigateToSchedule={onNavigateToSchedule}
|
||||
onNotesTagClick={onNotesTagClick}
|
||||
onSort={onSort}
|
||||
sortField={sortField}
|
||||
ascDesc={ascDesc}
|
||||
isFiltered={isFiltered}
|
||||
onReorder={allowReorder ? onReorder : undefined}
|
||||
onBatchDelete={onBatchDelete}
|
||||
onBatchDuplicate={onBatchDuplicate}
|
||||
onBatchLinkSchedule={onBatchLinkSchedule}
|
||||
onBatchUnlinkSchedule={onBatchUnlinkSchedule}
|
||||
onCreateRule={onCreateRule}
|
||||
onScheduleAction={onScheduleAction}
|
||||
onMakeAsNonSplitTransactions={onMakeAsNonSplitTransactions}
|
||||
showSelection={showSelection}
|
||||
allowSplitTransaction={allowSplitTransaction}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
6
upcoming-release-notes/7497.md
Normal file
6
upcoming-release-notes/7497.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [tempiz]
|
||||
---
|
||||
|
||||
Add scoped error boundaries to prevent feature-level crashes from taking down the entire app
|
||||
Reference in New Issue
Block a user