Skip to content

Commit

Permalink
[EuiErrorBoundary] Add onError callback prop (#6810)
Browse files Browse the repository at this point in the history
* [misc cleanup] convert tests to RTL; rename state

we're going to be grabbing the full `error` soon so we should make the state name a bit more descriptive

* Add new `onError` callback prop

* changelog

* Fix `onError` prop not showing up in prop docs

+ [misc] convert EuiErrorBoundary demo to tsx
  • Loading branch information
cee-chen authored May 25, 2023
1 parent 437c2c0 commit f2963e0
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 25 deletions.
42 changes: 25 additions & 17 deletions src/components/error_boundary/error_boundary.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
*/

import React from 'react';
import { mount } from 'enzyme';
import { requiredProps, takeMountedSnapshot } from '../../test';
import { render } from '../../test/rtl';
import { requiredProps } from '../../test';

import { EuiErrorBoundary } from './error_boundary';

Expand All @@ -24,40 +24,48 @@ const BadComponent = () => {
describe('EuiErrorBoundary', () => {
describe('without an error thrown', () => {
it('does not render the UI', () => {
const component = takeMountedSnapshot(
mount(
<EuiErrorBoundary {...requiredProps}>
<GoodComponent />
</EuiErrorBoundary>
)
const { container } = render(
<EuiErrorBoundary {...requiredProps}>
<GoodComponent />
</EuiErrorBoundary>
);

expect(component).toMatchSnapshot();
expect(container.firstChild).toMatchSnapshot();
});
});

describe('with an error thrown', () => {
it('renders UI', () => {
// Because the error contains the stack trace, it's non-deterministic. So we'll just check that
// it contains our error message.
const errorText = mount(
const { container } = render(
<EuiErrorBoundary {...requiredProps}>
<BadComponent />
</EuiErrorBoundary>
).text();
);

expect(errorText).toContain(errorMessage);
expect(container.textContent).toContain(errorMessage);
});

it('renders data-test-subj', () => {
const errorHtml = mount(
<EuiErrorBoundary {...requiredProps}>
const { getByTestSubject } = render(
<EuiErrorBoundary data-test-subj="test">
<BadComponent />
</EuiErrorBoundary>
).html();
);

expect(getByTestSubject('euiErrorBoundary test')).toBeTruthy();
});

it('calls onError', () => {
const onError = jest.fn();
render(
<EuiErrorBoundary onError={onError}>
<BadComponent />
</EuiErrorBoundary>
);

expect(errorHtml).toContain('euiErrorBoundary');
expect(errorHtml).toContain('test subject string');
expect(onError).toHaveBeenCalledWith(expect.any(Error));
});
});
});
26 changes: 18 additions & 8 deletions src/components/error_boundary/error_boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,20 @@ import { euiErrorBoundaryStyles } from './error_boundary.styles';

interface EuiErrorBoundaryState {
hasError: boolean;
error?: string;
errorMessage?: string;
}

export type EuiErrorBoundaryProps = CommonProps &
HTMLAttributes<HTMLDivElement> & {
Omit<HTMLAttributes<HTMLDivElement>, 'onError'> & {
/**
* ReactNode to render as this component's content
*/
children: ReactNode;

/**
* Callback that fires when an error is caught. Passes back the full error
*/
onError?: (error: Error) => void;
};

export class EuiErrorBoundary extends Component<
Expand All @@ -44,32 +49,37 @@ export class EuiErrorBoundary extends Component<

const errorState: EuiErrorBoundaryState = {
hasError: false,
error: undefined,
errorMessage: undefined,
};

this.state = errorState;
}

componentDidCatch({ message, stack }: Error) {
componentDidCatch(error: Error) {
// Display fallback UI
// Only Chrome includes the `message` property as part of `stack`.
// For consistency, rebuild the full error text from the Error subparts.
const { message, stack } = error;
const idx = stack?.indexOf(message) || -1;
const stackStr = idx > -1 ? stack?.substr(idx + message.length + 1) : stack;
const error = `Error: ${message}
const errorMessage = `Error: ${message}
${stackStr}`;
this.setState({
hasError: true,
error,
errorMessage,
});

// Pass back the error to the consumer
this.props.onError?.(error);
}

render() {
const { children, ...rest } = this.props;
const { hasError, errorMessage } = this.state;

if (this.state.hasError) {
if (hasError) {
// You can render any custom fallback UI
return <EuiErrorMessage {...rest} errorMessage={this.state.error} />;
return <EuiErrorMessage {...rest} errorMessage={errorMessage} />;
}

return children;
Expand Down
1 change: 1 addition & 0 deletions upcoming_changelogs/6810.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added an `onError` callback prop to `EuiErrorBoundary`

0 comments on commit f2963e0

Please sign in to comment.