Skip to content

Instantly share code, notes, and snippets.

@lancegliser
Last active January 15, 2020 21:11
Show Gist options
  • Save lancegliser/9a4d22dbab2113a45d5ddb04f5825071 to your computer and use it in GitHub Desktop.
Save lancegliser/9a4d22dbab2113a45d5ddb04f5825071 to your computer and use it in GitHub Desktop.
Basic error boundary component for Vue JS using the V2 composition api. Adapated from https://github.com/dillonchanis/vue-error-boundary
<script lang="ts">
import {
createComponent,
createElement,
onErrorCaptured,
reactive,
SetupContext
} from "@vue/composition-api";
import DefaultFallbackUI from "./ErrorBoundaryFallbackUI.vue";
import { IErrorBoundaryState } from "@/components/errorBoundary/IErrorBoundary";
import { Vue as VueType } from "vue/types/vue";
import { VNode } from "vue";
const isObjectEmpty = (obj: Object) =>
Object.entries(obj).length === 0 && obj.constructor === Object;
// https://github.com/posva/vue-promised/blob/master/src/index.js
const convertVNodeArray = (h: Function, wrapperTag: string, nodes: VNode[]) => {
// for arrays and single text nodes
if (nodes.length > 1 || !nodes[0].tag) return h(wrapperTag, {}, nodes);
return nodes[0];
};
interface IProps {
stopPropagation: boolean;
fallBack: Object;
onError: Function;
tag: string;
}
const defaultOnErrorFallback = () => {};
const ErrorBoundary = createComponent<IProps>({
name: "ErrorBoundary",
props: {
stopPropagation: {
type: Boolean,
default: false
},
fallBack: {
type: Object,
default: () => DefaultFallbackUI
},
onError: {
type: Function,
default: defaultOnErrorFallback
},
tag: {
type: String,
default: "div"
}
},
setup(props: IProps, context: SetupContext): () => VNode | null {
const { state } = useState(props);
return (): VNode | null => {
const fallback = createElement(props.fallBack, {
props: { state }
});
if (state.error) {
return Array.isArray(fallback)
? convertVNodeArray(createElement, props.tag, fallback)
: fallback;
}
if (isObjectEmpty(context.slots)) {
console.warn("ErrorBoundary component must have child components.");
return null;
}
const contents = context.slots.default();
return Array.isArray(contents)
? convertVNodeArray(createElement, props.tag, contents)
: contents;
};
}
});
export default ErrorBoundary;
function useState(props: IProps) {
const state = reactive({
error: undefined
} as IErrorBoundaryState);
onErrorCaptured((error: Error, vm: VueType, info: string = "") => {
state.error = error;
props.onError(error, vm, info);
if (props.stopPropagation) {
return false;
}
});
return { state };
}
</script>
<template>
<p>An error occurred</p>
<p v-if="isProduction">{{ state.error.message }}</p>
<pre v-else>
{{ state.error.stack }}
</pre>
</template>
<script lang="ts">
import { computed, createComponent } from "@vue/composition-api";
import { IErrorBoundaryState } from "@/components/errorBoundary/IErrorBoundary";
interface IProps {
state: IErrorBoundaryState;
}
const ErrorBoundaryFallbackUI = createComponent<IProps>({
props: {
state: Object
},
setup() {
const isProduction = computed(() => process.env.NODE_ENV === "production");
return { isProduction };
}
});
export default ErrorBoundaryFallbackUI;
</script>
export interface IErrorBoundaryState {
error: Error | undefined;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment