Skip to content

Commit 2bf87c4

Browse files
committed
feat: show errors on current page instead of navigating to error page
KBDEV-1488 show error either as alert on page or use snackbar for pages where data is necessary to render page in any meaningful way, throw error to boundary
1 parent 0821498 commit 2bf87c4

29 files changed

Lines changed: 3951 additions & 450 deletions

File tree

.storybook/preview.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { SnackbarProvider } from 'notistack';
1717
import { AuthContext, AuthContextState } from '../src/components/Auth';
1818
import { expect, waitFor } from 'storybook/test';
1919
import { CircularProgress } from '@mui/material';
20+
import ErrorView from '../src/views/ErrorView';
2021

2122
function wrapAsResponse<T extends Record<string, any> | any[]>(maybeResponse: T | HttpResponse<T>): HttpResponse<T> {
2223
if (maybeResponse instanceof Response) {
@@ -148,9 +149,11 @@ export interface ViewPreviewType {
148149
function View() {
149150
return (
150151
<div style={{height: '100%', minWidth: '300px'}}>
152+
<ErrorView>
151153
<Suspense fallback={(<CircularProgress color="secondary" />)}>
152154
<AppRoutes />
153155
</Suspense>
156+
</ErrorView>
154157
</div>
155158
)
156159
}

src/components/DetailDrawer/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ interface DetailDrawerProps {
5454
/** Ontology to be displayed in drawer. */
5555
node?: GeneralRecordType;
5656
onClose?: () => void;
57+
error?: ReactNode;
5758
}
5859

5960
/**
@@ -65,6 +66,7 @@ function DetailDrawer(props: DetailDrawerProps) {
6566
node,
6667
onClose,
6768
isEdge = false,
69+
error,
6870
} = props;
6971
const auth = useAuth();
7072

@@ -249,6 +251,7 @@ function DetailDrawer(props: DetailDrawerProps) {
249251
<CloseIcon />
250252
</IconButton>
251253
</div>
254+
{error}
252255
<Divider />
253256
{otherProps}
254257
<ListItem

src/components/RecordForm/__tests__/index.test.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ describe('RecordForm', () => {
6666
});
6767

6868
const onSubmitSpy = vi.fn();
69-
const onErrorSpy = vi.fn();
7069
const onToggleStateSpy = vi.fn();
7170
const snackbarSpy = vi.fn();
7271

@@ -78,7 +77,6 @@ describe('RecordForm', () => {
7877
<SnackbarProvider onEnter={snackbarSpy}>
7978
<RecordForm
8079
modelName="User"
81-
onError={onErrorSpy}
8280
onSubmit={onSubmitSpy}
8381
onToggleState={onToggleStateSpy}
8482
title="blargh monkeys"
@@ -127,7 +125,6 @@ describe('RecordForm', () => {
127125
<SnackbarProvider onEnter={snackbarSpy}>
128126
<RecordForm
129127
modelName="User"
130-
onError={onErrorSpy}
131128
onSubmit={onSubmitSpy}
132129
onToggleState={onToggleStateSpy}
133130
title="blargh monkeys"
@@ -175,7 +172,6 @@ describe('RecordForm', () => {
175172
<AuthContext.Provider value={auth}>
176173
<RecordForm
177174
modelName="User"
178-
onError={onErrorSpy}
179175
onSubmit={onSubmitSpy}
180176
onToggleState={onToggleStateSpy}
181177
title="blargh monkeys"

src/components/RecordForm/index.tsx

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import RecordFormStateToggle from '@/components/RecordFormStateToggle';
1717
import { GeneralRecordType } from '@/components/types';
1818
import { cleanPayload, FORM_VARIANT } from '@/components/util';
1919
import api from '@/services/api';
20+
import { ErrorMessage } from '@/services/errors';
2021
import schema from '@/services/schema';
2122

2223
import { useAuth } from '../Auth';
@@ -31,7 +32,6 @@ interface RecordFormProps {
3132
title: string;
3233
/** name of class model to be displayed */
3334
modelName?: string;
34-
onError?: (arg: { error: { name?: string; message?: string }; content: unknown }) => void;
3535
onSubmit?: (record?: GeneralRecordType) => void;
3636
onToggleState?: (newState: FORM_VARIANT | 'graph') => void;
3737
/** values of individual properties of passed class model */
@@ -49,7 +49,6 @@ const RecordForm = ({
4949
title,
5050
onToggleState,
5151
onSubmit,
52-
onError,
5352
variant = FORM_VARIANT.VIEW,
5453
}: RecordFormProps) => {
5554
const snackbar = useSnackbar();
@@ -63,7 +62,7 @@ const RecordForm = ({
6362
formIsDirty, setFormIsDirty, formContent, formErrors, formHasErrors,
6463
} = form;
6564

66-
const { mutate: addNewAction, isPending: isAdding } = useMutation({
65+
const { mutate: addNewAction, isPending: isAdding, error: errorAdding } = useMutation({
6766
mutationFn: async (content: GeneralRecordType) => {
6867
const payload = cleanPayload(content);
6968
const { routeName } = schemaDefn.get(payload);
@@ -73,11 +72,6 @@ const RecordForm = ({
7372
snackbar.enqueueSnackbar(`Sucessfully created the record ${result['@rid']}`, { variant: 'success' });
7473
onSubmit?.(result);
7574
},
76-
onError: (err: Error, content) => {
77-
console.error(err);
78-
snackbar.enqueueSnackbar(`Error (${err.name}) in creating the record`, { variant: 'error' });
79-
onError?.({ error: err, content });
80-
},
8175
});
8276

8377
/**
@@ -101,7 +95,7 @@ const RecordForm = ({
10195
}
10296
}, [addNewAction, formContent, formErrors, formHasErrors, modelName, setFormIsDirty, snackbar]);
10397

104-
const { mutate: deleteAction, isPending: isDeleting } = useMutation({
98+
const { mutate: deleteAction, isPending: isDeleting, error: errorDeleting } = useMutation({
10599
mutationFn: async (content: GeneralRecordType) => {
106100
const { routeName } = schemaDefn.get(content);
107101
return api.delete(`${routeName}/${content['@rid']!.replace(/^#/, '')}`);
@@ -110,10 +104,6 @@ const RecordForm = ({
110104
snackbar.enqueueSnackbar(`Successfully deleted the record ${content['@rid']}`, { variant: 'success' });
111105
onSubmit?.();
112106
},
113-
onError: (err: Error, content) => {
114-
snackbar.enqueueSnackbar(`Error (${err.name}) in deleting the record (${content['@rid']})`, { variant: 'error' });
115-
onError?.({ error: err, content });
116-
},
117107
});
118108

119109
/**
@@ -128,7 +118,7 @@ const RecordForm = ({
128118
deleteAction(content);
129119
}, [deleteAction, formContent, modelName]);
130120

131-
const { mutate: updateAction, isPending: isUpdating } = useMutation({
121+
const { mutate: updateAction, isPending: isUpdating, error: errorUpdating } = useMutation({
132122
mutationFn: async (content: GeneralRecordType) => {
133123
const payload = cleanPayload(content);
134124
const { routeName } = schemaDefn.get(payload);
@@ -138,10 +128,6 @@ const RecordForm = ({
138128
snackbar.enqueueSnackbar(`Successfully edited the record ${result['@rid']}`, { variant: 'success' });
139129
onSubmit?.(result);
140130
},
141-
onError: (err: Error, content) => {
142-
snackbar.enqueueSnackbar(`Error (${err.name}) in editing the record (${content['@rid']})`, { variant: 'error' });
143-
onError?.({ error: err, content });
144-
},
145131
});
146132

147133
/**
@@ -215,6 +201,9 @@ const RecordForm = ({
215201
)}
216202
</>
217203
)}
204+
<ErrorMessage error={errorAdding}>An error occurred while creating record.</ErrorMessage>
205+
<ErrorMessage error={errorDeleting}>An error occurred while deleting record.</ErrorMessage>
206+
<ErrorMessage error={errorUpdating}>An error occurred while updating record.</ErrorMessage>
218207
<div className="record-form__action-buttons">
219208
{variant === FORM_VARIANT.EDIT && !formContent.deletedAt
220209
? (

src/components/RecordFormDialog/index.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ const RecordFormDialog = (props: RecordFormDialogProps) => {
2929
isOpen = false,
3030
modelName,
3131
onClose,
32-
onError,
3332
onSubmit,
3433
title = '',
3534
variant,
@@ -65,7 +64,6 @@ const RecordFormDialog = (props: RecordFormDialogProps) => {
6564
<DialogContent>
6665
<RecordForm
6766
modelName={modelName}
68-
onError={onError}
6967
onSubmit={onSubmit}
7068
onToggleState={onToggleState}
7169
title={title}

src/components/StatementForm/index.stories.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ const meta = preview.meta({
1414
decorators: [withSnackbar, withAuth, withRouter],
1515
args: {
1616
title: 'blargh monkeys',
17-
onError: fn(),
1817
onSubmit: fn(),
1918
value: {},
2019
},

src/components/StatementForm/index.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import RecordFormStateToggle from '@/components/RecordFormStateToggle';
2424
import { GeneralRecordType } from '@/components/types';
2525
import { cleanPayload, FORM_VARIANT, tuple } from '@/components/util';
2626
import api from '@/services/api';
27+
import { ErrorMessage } from '@/services/errors';
2728

2829
import CivicEvidenceLink from './CivicEvidenceLink';
2930
import ReviewDialog from './ReviewDialog';
@@ -33,7 +34,6 @@ const FIELD_EXCLUSIONS = ['groupRestrictions'];
3334
interface StatementFormProps {
3435
/** the title for this form */
3536
title: string;
36-
onError?: (arg: { error: { name?: string; message?: string }; content: unknown }) => void;
3737
onSubmit?: (record?: GeneralRecordType) => void;
3838
onToggleState?: (newState: FORM_VARIANT | 'graph') => void;
3939
/** values of individual properties of passed class model */
@@ -50,7 +50,6 @@ const StatementForm = ({
5050
title,
5151
onToggleState,
5252
onSubmit,
53-
onError,
5453
variant = FORM_VARIANT.VIEW,
5554
}: StatementFormProps) => {
5655
const params = useParams();
@@ -109,6 +108,7 @@ const StatementForm = ({
109108
queryKey: [`/statements/${params.rid}?neighbors=1`],
110109
queryFn: async ({ queryKey: [route] }) => api.get(route),
111110
enabled: (isempty(initialValue) && Boolean(params.rid)),
111+
throwOnError: true,
112112
});
113113

114114
const snackbar = useSnackbar();
@@ -208,7 +208,7 @@ const StatementForm = ({
208208
return updatedContent;
209209
}, [auth]);
210210

211-
const { mutate: addNewAction, isPending: isAdding } = useMutation({
211+
const { mutate: addNewAction, isPending: isAdding, error: errorAdding } = useMutation({
212212
mutationFn: async (content: GeneralRecordType) => {
213213
const payload = cleanPayload(content);
214214
const { routeName } = schemaDefn.get(payload);
@@ -218,11 +218,6 @@ const StatementForm = ({
218218
snackbar.enqueueSnackbar(`Sucessfully created the record ${result['@rid']}`, { variant: 'success' });
219219
onSubmit?.(result);
220220
},
221-
onError: (err: Error, content) => {
222-
console.error(err);
223-
snackbar.enqueueSnackbar(`Error (${err.name}) in creating the record`, { variant: 'error' });
224-
onError?.({ error: err, content });
225-
},
226221
});
227222

228223
/**
@@ -242,7 +237,7 @@ const StatementForm = ({
242237
}
243238
}, [addNewAction, formContent, formErrors, formHasErrors, model.name, setFormIsDirty, snackbar, statementReviewCheck]);
244239

245-
const { mutate: deleteAction, isPending: isDeleting } = useMutation({
240+
const { mutate: deleteAction, isPending: isDeleting, error: errorDeleting } = useMutation({
246241
mutationFn: async (content: GeneralRecordType) => {
247242
const { routeName } = schemaDefn.get(content);
248243
return api.delete(`${routeName}/${content['@rid']!.replace(/^#/, '')}`);
@@ -251,10 +246,6 @@ const StatementForm = ({
251246
snackbar.enqueueSnackbar(`Sucessfully deleted the record ${content['@rid']}`, { variant: 'success' });
252247
onSubmit?.();
253248
},
254-
onError: (err: Error, content) => {
255-
snackbar.enqueueSnackbar(`Error (${err.name}) in deleting the record (${content['@rid']})`, { variant: 'error' });
256-
onError?.({ error: err, content });
257-
},
258249
});
259250

260251
/**
@@ -265,7 +256,7 @@ const StatementForm = ({
265256
deleteAction(content);
266257
}, [deleteAction, formContent, model.name]);
267258

268-
const { mutate: updateAction, isPending: isUpdating } = useMutation({
259+
const { mutate: updateAction, isPending: isUpdating, error: errorUpdating } = useMutation({
269260
mutationFn: async (content: GeneralRecordType) => {
270261
const payload = cleanPayload(content);
271262
const { routeName } = schemaDefn.get(payload);
@@ -275,10 +266,6 @@ const StatementForm = ({
275266
snackbar.enqueueSnackbar(`Sucessfully edited the record ${result['@rid']}`, { variant: 'success' });
276267
onSubmit?.(result);
277268
},
278-
onError: (err: Error, content) => {
279-
snackbar.enqueueSnackbar(`Error (${err.name}) in editing the record (${content['@rid']})`, { variant: 'error' });
280-
onError?.({ error: err, content });
281-
},
282269
});
283270

284271
/**
@@ -381,6 +368,9 @@ const StatementForm = ({
381368
modelName={model.name}
382369
/>
383370
</FormContext.Provider>
371+
<ErrorMessage error={errorAdding}>An error occurred while creating record.</ErrorMessage>
372+
<ErrorMessage error={errorDeleting}>An error occurred while deleting record.</ErrorMessage>
373+
<ErrorMessage error={errorUpdating}>An error occurred while updating record.</ErrorMessage>
384374
<div className="statement-form__action-buttons">
385375
{variant === FORM_VARIANT.EDIT && !formContent.deletedAt
386376
? (

src/components/VariantForm/SteppedForm/index.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ interface SteppedFormProps {
3030
formVariant?: FORM_VARIANT;
3131
isLoading?: boolean;
3232
value?: Record<string, unknown>;
33+
errors?: ReactNode;
3334
}
3435

3536
const SteppedForm = ({
36-
children, modelName, properties, onSubmit, className = '', value = {}, formVariant = FORM_VARIANT.NEW, onDelete, isLoading = false,
37+
children, modelName, properties, onSubmit, className = '', value = {}, formVariant = FORM_VARIANT.NEW, onDelete, isLoading = false, errors,
3738
}: SteppedFormProps) => {
3839
const snackbar = useSnackbar();
3940
const [activeStep, setActiveStep] = useState(0);
@@ -102,6 +103,7 @@ const SteppedForm = ({
102103
</Step>
103104
);
104105
})}
106+
{errors}
105107
<div className="stepped-form__actions">
106108
{formVariant === FORM_VARIANT.EDIT && (
107109
<ActionButton

src/components/VariantForm/__tests__/index.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('NewVariant', () => {
2727
getByText, getByTestId, queryByText,
2828
} = render(
2929
<QueryClientProvider client={api.queryClient}>
30-
<NewVariant onError={vi.fn()} onSubmit={vi.fn()} />
30+
<NewVariant onSubmit={vi.fn()} />
3131
</QueryClientProvider>,
3232
));
3333
});

src/components/VariantForm/index.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import FieldGroup from '@/components/FormLayout/FieldGroup';
1616
import RadioSelect from '@/components/RadioSelect';
1717
import { cleanPayload, FORM_VARIANT, sortAndGroupFields } from '@/components/util';
1818
import api from '@/services/api';
19+
import { ErrorMessage } from '@/services/errors';
1920

2021
import { GeneralRecordType } from '../types';
2122
import BreakpointForm from './BreakpointForm';
@@ -92,8 +93,6 @@ const pickInputType = (record) => {
9293
};
9394

9495
interface VariantFormProps {
95-
/** the handler to be called when the submission throws an error */
96-
onError: (arg: { error: { name?: string; message?: string }; content: unknown }) => void;
9796
/** the handler to be called when the form is submitted */
9897
onSubmit: (record?: GeneralRecordType | null) => void;
9998
formVariant?: FORM_VARIANT;
@@ -104,7 +103,7 @@ interface VariantFormProps {
104103
* Input form for new Variants
105104
*/
106105
const VariantForm = ({
107-
onSubmit, onError, value = {}, formVariant = FORM_VARIANT.NEW,
106+
onSubmit, value = {}, formVariant = FORM_VARIANT.NEW,
108107
}: VariantFormProps) => {
109108
let defaultCoordinateType;
110109

@@ -207,12 +206,6 @@ const VariantForm = ({
207206
snackbar.enqueueSnackbar(`Sucessfully ${actionType} the record ${result['@rid']}`, { variant: 'success' });
208207
onSubmit(result);
209208
},
210-
onError: (error: any, content) => {
211-
const actionType = formVariant === FORM_VARIANT.NEW ? 'creating' : 'editing';
212-
console.error(error);
213-
snackbar.enqueueSnackbar(`Error (${error.name}) in ${actionType} the record`, { variant: 'error' });
214-
onError({ error, content });
215-
},
216209
});
217210

218211
const deleteMutation = useMutation({
@@ -226,11 +219,6 @@ const VariantForm = ({
226219
snackbar.enqueueSnackbar(`Sucessfully deleted the record ${result['@rid']}`, { variant: 'success' });
227220
onSubmit(null);
228221
},
229-
onError: (error: any, content) => {
230-
console.error(error);
231-
snackbar.enqueueSnackbar(`Error (${error.name}) in deleting the record`, { variant: 'error' });
232-
onError({ error, content });
233-
},
234222
});
235223

236224
const handleSubmitAction = useCallback((content) => {
@@ -244,6 +232,12 @@ const VariantForm = ({
244232
return model && (
245233
<SteppedForm
246234
className="new-variant"
235+
errors={(
236+
<>
237+
<ErrorMessage error={submitMutation.error}>An error occurred while creating/editing record.</ErrorMessage>
238+
<ErrorMessage error={deleteMutation.error}>An error occurred while deleting record.</ErrorMessage>
239+
</>
240+
)}
247241
formVariant={formVariant}
248242
isLoading={submitMutation.isPending || deleteMutation.isPending}
249243
modelName={model.name}

0 commit comments

Comments
 (0)