src/Umbraco.Web.UI.Client/docs/error-handling.md
← Umbraco Backoffice | ← Monorepo Root
element.updateCompleteCommon Web Component Issues:
@property() decorator and reactive update cycleshadowRoot.querySelector() not querySelector()Error Classes:
// Use built-in Error class or extend it
throw new Error('Failed to load content');
// Custom error classes for domain errors
export class UmbContentNotFoundError extends Error {
constructor(id: string) {
super(`Content with id "${id}" not found`);
this.name = 'UmbContentNotFoundError';
}
}
Repository Pattern Error Handling:
async requestById(id: string): Promise<{ data?: UmbContentModel; error?: Error }> {
try {
const response = await this._apiClient.getById({ id });
return { data: response.data };
} catch (error) {
return { error: error as Error };
}
}
// Usage
const { data, error } = await repository.requestById('123');
if (error) {
// Handle error
console.error('Failed to load content:', error);
return;
}
// Use data
Observable Error Handling:
this.observe(dataSource$, (value) => {
// Success handler
this._data = value;
}).catch((error) => {
// Error handler
console.error('Observable error:', error);
});
Promise Error Handling:
// Always use try/catch with async/await
async myMethod() {
try {
const result = await this.fetchData();
return result;
} catch (error) {
console.error('Failed to fetch data:', error);
throw error; // Re-throw or handle
}
}
// Or use .catch()
this.fetchData()
.then(result => this.handleResult(result))
.catch(error => this.handleError(error));
Lifecycle Errors:
export class UmbMyElement extends UmbElementMixin(LitElement) {
constructor() {
try {
super();
// Initialization that might throw
} catch (error) {
console.error('Failed to initialize element:', error);
}
}
async connectedCallback() {
try {
super.connectedCallback();
// Async initialization
await this.loadData();
} catch (error) {
console.error('Failed to connect element:', error);
this._errorMessage = 'Failed to load component';
}
}
}
Render Errors:
override render() {
if (this._error) {
return html`<umb-error-message .error=${this._error}></umb-error-message>`;
}
if (!this._data) {
return html`<uui-loader></uui-loader>`;
}
return html`
<!-- Normal render -->
`;
}
Console Logging:
// Development only - Remove before production
console.log('Debug info:', data);
// Errors - Kept in production but sanitized
console.error('Failed to load:', error);
// Warnings
console.warn('Deprecated API usage:', method);
// Avoid console.log in production code (ESLint warning)
Custom Logging (if needed):
// Use debug flag for verbose logging
if (this._debug) {
console.log('[UmbMyComponent]', 'State changed:', this._state);
}
Don't Log Sensitive Data:
Production Error Display:
private _errorMessage?: string;
override render() {
if (this._errorMessage) {
return html`
<uui-box>
<p class="error">${this._errorMessage}</p>
<uui-button @click=${this._retry} label="Try Again"></uui-button>
</uui-box>
`;
}
// ... normal render
}
HTTP Client Errors (OpenAPI):
try {
const response = await this._apiClient.getDocument({ id });
return response.data;
} catch (error) {
if (error.status === 404) {
throw new UmbContentNotFoundError(id);
}
if (error.status === 403) {
throw new UmbUnauthorizedError('Access denied');
}
throw error;
}
Observable Subscription Errors:
this._subscription = this._dataSource.asObservable().subscribe({
next: (value) => this._data = value,
error: (error) => {
console.error('Observable error:', error);
this._errorMessage = 'Failed to load data';
},
complete: () => console.log('Observable completed'),
});