Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a global ErrorBoundary #9799

Merged
merged 12 commits into from
May 3, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 127 additions & 22 deletions docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,28 +136,30 @@ Three main props lets you configure the core features of the `<Admin>` component

Here are all the props accepted by the component:

| Prop | Required | Type | Default | Description |
|------------------- |----------|----------------|----------------|----------------------------------------------------------|
| `dataProvider` | Required | `DataProvider` | - | The data provider for fetching resources |
| `children` | Required | `ReactNode` | - | The routes to render |
| `authCallbackPage` | Optional | `Component` | `AuthCallback` | The content of the authentication callback page |
| `authProvider` | Optional | `AuthProvider` | - | The authentication provider for security and permissions |
| `basename` | Optional | `string` | - | The base path for all URLs |
| `catchAll` | Optional | `Component` | `NotFound` | The fallback component for unknown routes |
| `dashboard` | Optional | `Component` | - | The content of the dashboard page |
| `darkTheme` | Optional | `object` | `default DarkTheme` | The dark theme configuration |
| `defaultTheme` | Optional | `boolean` | `false` | Flag to default to the light theme |
| `disableTelemetry` | Optional | `boolean` | `false` | Set to `true` to disable telemetry collection |
| `i18nProvider` | Optional | `I18NProvider` | - | The internationalization provider for translations |
| `layout` | Optional | `Component` | `Layout` | The content of the layout |
| `loginPage` | Optional | `Component` | `LoginPage` | The content of the login page |
| `notification` | Optional | `Component` | `Notification` | The notification component |
| `queryClient` | Optional | `QueryClient` | - | The react-query client |
| `ready` | Optional | `Component` | `Ready` | The content of the ready page |
| `requireAuth` | Optional | `boolean` | `false` | Flag to require authentication for all routes |
| `store` | Optional | `Store` | - | The Store for managing user preferences |
| `theme` | Optional | `object` | `default LightTheme` | The main (light) theme configuration |
| `title` | Optional | `string` | - | The error page title |
| Prop | Required | Type | Default | Description |
|------------------- |----------|------------------------------------------ |--------------------- |---------------------------------------------------------------- |
| `dataProvider` | Required | `DataProvider` | - | The data provider for fetching resources |
| `children` | Required | `ReactNode` | - | The routes to render |
| `authCallbackPage` | Optional | `Component` | `AuthCallback` | The content of the authentication callback page |
| `authProvider` | Optional | `AuthProvider` | - | The authentication provider for security and permissions |
| `basename` | Optional | `string` | - | The base path for all URLs |
| `catchAll` | Optional | `Component` | `NotFound` | The fallback component for unknown routes |
| `dashboard` | Optional | `Component` | - | The content of the dashboard page |
| `darkTheme` | Optional | `object` | `default DarkTheme` | The dark theme configuration |
| `defaultTheme` | Optional | `boolean` | `false` | Flag to default to the light theme |
| `disableTelemetry` | Optional | `boolean` | `false` | Set to `true` to disable telemetry collection |
| `error` | Optional | `(props: FallbackProps) => Component` | - | A React component rendered in the content area in case of error |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type is too precise and therefore takes too much space. Use Component as for the other similar props

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

| `i18nProvider` | Optional | `I18NProvider` | - | The internationalization provider for translations |
| `layout` | Optional | `Component` | `Layout` | The content of the layout |
| `loginPage` | Optional | `Component` | `LoginPage` | The content of the login page |
| `notification` | Optional | `Component` | `Notification` | The notification component |
| `onError` | Optional | `(error: Error, info: ErrorInfo) => void` | - | A function called when an error appears |
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved
| `queryClient` | Optional | `QueryClient` | - | The react-query client |
| `ready` | Optional | `Component` | `Ready` | The content of the ready page |
| `requireAuth` | Optional | `boolean` | `false` | Flag to require authentication for all routes |
| `store` | Optional | `Store` | - | The Store for managing user preferences |
| `theme` | Optional | `object` | `default LightTheme` | The main (light) theme configuration |
| `title` | Optional | `string` | - | The error page title |


## `dataProvider`
Expand Down Expand Up @@ -521,6 +523,70 @@ const App = () => (
```


## `error`

Whenever some client-side errors happens in react-admin, the user sees an error page. React-admin uses [React's Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) to render this page when any component in the page throws an unrecoverable error.
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved

![Default error page](./img/adminError.png)

If you want to customize this page, or log the error to a third-party service, create your own `<Error>` component, and pass it to a custom Layout, as follows:

```jsx
// in src/App.js
import { Admin } from 'react-admin';
import { MyError } from './MyError';

export const MyLayout = ({ children }) => (
<Admin error={({ props }) => <MyError {...props} />}>
erwanMarmelab marked this conversation as resolved.
Show resolved Hide resolved
{children}
</Admin>
);
```

```jsx
// in src/MyError.js
import * as React from 'react';
import Button from '@mui/material/Button';
import ErrorIcon from '@mui/icons-material/Report';
import History from '@mui/icons-material/History';
import { useLocation } from 'react-router-dom';

export const MyError = ({
error,
resetErrorBoundary,
...rest
}) => {
const { pathname } = useLocation();
const originalPathname = useRef(pathname);

// Effect that resets the error state whenever the location changes
useEffect(() => {
if (pathname !== originalPathname.current) {
resetErrorBoundary();
}
}, [pathname, resetErrorBoundary]);

return (
<div>
<h1><ErrorIcon /> Something Went Wrong </h1>
<div>A client error occurred and your request couldn't be completed.</div>
<div>
<Button
variant="contained"
startIcon={<History />}
onClick={() => history.go(-1)}
>
Back
</Button>
</div>
</div>
);
};
```

**Tip:** Some errors appear in a lower level than the admin. To customize this error page too, you can use the [`<Layout>` `error` prop](./Layout.md#error).
fzaninotto marked this conversation as resolved.
Show resolved Hide resolved


## `i18nProvider`

The `i18nProvider` props let you translate the GUI. For instance, to switch the UI to French instead of the default English:
Expand Down Expand Up @@ -691,6 +757,45 @@ const App = () => (
);
```

## `onError`

When a error apears, you can override the react-admin function called to grab some informations as follows:

```jsx
// in src/App.js
import * as React from 'react';
import { Admin } from 'react-admin';
import { MyError } from './MyError';

export const MyLayout = ({ children }) => {
const [errorInfo, setErrorInfo] = React.useState<React.ErrorInfo | undefined>(undefined);
return (
<Admin
error={({ props }) => <MyError {...props} errorInfo={errorInfo} />}
onError={(error: Error, info: ErrorInfo) => setErrorInfo(info)}
>
{children}
</Admin>
);
}
```

```jsx
// in src/MyError.js
import * as React from 'react';
import ErrorIcon from '@mui/icons-material/Report';

export const MyError = ({errorInfo: React.ErrorInfo}) => (
<div>
<h1><ErrorIcon /> Something Went Wrong </h1>
<div>
<p>A client error occurred and your request couldn't be completed.</p>
<p>Error informations: {JSON.stringify(errorInfo)}</p>
</div>
</div>
);
```

## `notification`

You can override the notification component, for instance to change the notification duration. A common use case is to change the `autoHideDuration`, and force the notification to remain on screen longer than the default 4 seconds. For instance, to create a custom Notification component with a 5 seconds default:
Expand Down
2 changes: 2 additions & 0 deletions docs/Layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ export const MyError = ({

**Tip:** [React's Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) are used internally to display the Error Page whenever an error occurs. Error Boundaries only catch errors during rendering, in lifecycle methods, and in constructors of the components tree. This implies in particular that errors during event callbacks (such as 'onClick') are not concerned. Also note that the Error Boundary component is only set around the main container of React Admin. In particular, you won't see it for errors thrown by the [sidebar Menu](./Menu.md), nor the [AppBar](#adding-a-custom-context). This ensures the user is always able to navigate away from the Error Page.

**Tip:** Some errors appear in a higher level than the layout. To customize this error page too, you can use the [`<Admin>` `error` prop](./Admin.md#error).
erwanMarmelab marked this conversation as resolved.
Show resolved Hide resolved

## `menu`

Lets you override the menu.
Expand Down
Binary file added docs/img/adminError.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions packages/ra-core/src/core/CoreAdmin.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import * as React from 'react';
import { Route } from 'react-router';
import { CoreAdmin } from './CoreAdmin';
import { CustomRoutes } from './CustomRoutes';
import { FakeBrowserDecorator } from '../storybook/FakeBrowser';

export default {
title: 'ra-core/Admin',
decorators: [FakeBrowserDecorator],
parameters: {
initialEntries: ['/error'],
},
};

const ErrorComponent = () => {
throw Error();
};

export const Error = () => (
<CoreAdmin>
<CustomRoutes noLayout>
<Route path="/error" element={<ErrorComponent />} />
</CustomRoutes>
</CoreAdmin>
);

export const CustomError = () => (
<CoreAdmin error={() => <h1>Something went wrong...</h1>}>
<CustomRoutes noLayout>
<Route path="/error" element={<ErrorComponent />} />
</CustomRoutes>
</CoreAdmin>
);
4 changes: 4 additions & 0 deletions packages/ra-core/src/core/CoreAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export const CoreAdmin = (props: CoreAdminProps) => {
dashboard,
dataProvider,
disableTelemetry,
error,
onError,
i18nProvider,
queryClient,
layout,
Expand All @@ -117,6 +119,8 @@ export const CoreAdmin = (props: CoreAdminProps) => {
catchAll={catchAll}
title={title}
loading={loading}
error={error}
onError={onError}
loginPage={loginPage}
requireAuth={requireAuth}
ready={ready}
Expand Down
Loading
Loading