Skip to content

Loading State & Error Boundary

This page demonstrates the advanced features of Monaco Editor Vue3, including loading state management, error handling, and enhanced lifecycle hooks.

Overview

Monaco Editor Vue3 provides comprehensive state management and error handling mechanisms:

  • Loading state management: Display editor loading progress and status
  • Error boundary: Capture and handle runtime errors in the editor
  • Lifecycle hooks: Execute custom logic at different stages of the editor
  • Retry mechanism: Allow users to reload when errors occur
  • Hook API: Low-level API for custom implementations

1. Basic Usage (with Error Handling & Loading State)

Enhanced CodeEditor Example

vue
<template>
  <div>
    <h3>Code Editor with Enhanced Features</h3>
    <!-- Basic usage -->
    <CodeEditor
      v-model:value="code"
      language="javascript"
      theme="vs-dark"
      :height="400"
      :lifecycle="lifecycleHooks"
      @error="handleError"
      @ready="handleReady"
      @loading="handleLoading"
    />
    <!-- Show state -->
    <div v-if="editorError" class="error-info">
      <h4>Error: {{ editorError.code }}</h4>
      <p>{{ editorError.message }}</p>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { CodeEditor, type EditorLifecycleHooks, type EditorError } from 'monaco-editor-vue3';

const code = ref(`function hello() {
  console.log('Hello Monaco Editor Vue3!');
  return 'Welcome to enhanced editor!';
}`);

const editorError = ref<EditorError | null>(null);

// Lifecycle hooks
const lifecycleHooks: EditorLifecycleHooks = {
  beforeCreate: async () => {
    console.log('Editor will be created...');
  },
  onCreated: (editor) => {
    console.log('Editor created:', editor);
  },
  onReady: (editor) => {
    console.log('Editor is ready:', editor);
  },
  onError: (error) => {
    console.error('Lifecycle error:', error);
  }
};

const handleError = (error: EditorError | null) => {
  editorError.value = error;
  if (error) {
    console.error('Editor error:', error);
  }
};

const handleReady = () => {
  console.log('Editor is ready for use');
};

const handleLoading = (loadingState: any) => {
  console.log('Loading state:', loadingState);
};
</script>

Enhanced DiffEditor Example

vue
<template>
  <div>
    <h3>Diff Editor with Enhanced Features</h3>
    <DiffEditor
      v-model:value="modifiedCode"
      :original="originalCode"
      language="typescript"
      theme="vs"
      :height="400"
      :lifecycle="lifecycleHooks"
      loading-text="Loading diff editor..."
      :show-progress="true"
      @error="handleDiffError"
      @ready="handleDiffReady"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { DiffEditor, type EditorLifecycleHooks } from 'monaco-editor-vue3';

const originalCode = ref(`interface User {
  id: number;
  name: string;
}`);

const modifiedCode = ref(`interface User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;
}`);

const lifecycleHooks: EditorLifecycleHooks = {
  onReady: (editor) => {
    console.log('Diff editor ready');
  }
};

const handleDiffError = (error: any) => {
  console.error('Diff editor error:', error);
};

const handleDiffReady = () => {
  console.log('Diff editor is ready');
};
</script>

2. Advanced Feature Examples

Using the Hook API Directly

vue
<template>
  <div>
    <div ref="container" style="height: 400px; border: 1px solid #ccc;"></div>
    <div v-if="loading.isLoading" class="loading">
      Loading: {{ loading.progress }}%
    </div>
    <div v-if="error" class="error">
      Error: {{ error.message }}
      <button @click="retry">Retry</button>
    </div>
    <button v-if="isReady" @click="destroy">Destroy Editor</button>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { useCodeEditor, type EditorLifecycleHooks } from 'monaco-editor-vue3';

const container = ref<HTMLElement>();

const props = {
  value: 'console.log("Hello from hook!");',
  language: 'javascript',
  theme: 'vs-dark',
  lifecycle: {
    onReady: (editor: any) => {
      console.log('Hook: Editor ready', editor);
    }
  } as EditorLifecycleHooks
};

const emit = (event: string, ...args: any[]) => {
  console.log('Event:', event, args);
};

const { loading, error, isReady, retry, destroy } = useCodeEditor(props, emit);
</script>

Standalone Error Boundary Component

vue
<template>
  <div>
    <MonacoErrorBoundary
      :error="sampleError"
      @retry="handleRetry"
      @report="handleReport"
    />
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { MonacoErrorBoundary, createEditorError } from 'monaco-editor-vue3';

const sampleError = ref(createEditorError(
  'DEMO_ERROR',
  'This is a demonstration error',
  'Stack trace would appear here...',
  true
));

const handleRetry = () => {
  console.log('User clicked retry');
  sampleError.value = null;
};

const handleReport = (error: any) => {
  console.log('Report error:', error);
};
</script>

3. Custom Loading & Error Components

Monaco Editor Vue3 supports fully custom loading and error state displays.

Using Default Component Configuration

vue
<template>
  <CodeEditor
    v-model:value="code"
    :loading-text="'Loading editor...'"
    :show-progress="true"
    :show-error-boundary="true"
    :retryable="true"
  />
</template>

Custom Loading Component

vue
<template>
  <CodeEditor v-model:value="code">
    <template #loading="{ loading, loadingText, progress, showProgress }">
      <div class="my-custom-loading">
        <div class="custom-spinner">🔄</div>
        <h3>{{ loadingText }}</h3>
        <div v-if="showProgress" class="progress-info">
          <span>Progress: {{ progress }}%</span>
          <div class="custom-progress-bar">
            <div 
              class="progress-fill" 
              :style="{ width: `${progress}%` }"
            ></div>
          </div>
        </div>
        <p>Status: {{ loading.stage }}</p>
      </div>
    </template>
  </CodeEditor>
</template>

<style scoped>
.my-custom-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
}

.custom-spinner {
  font-size: 2rem;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.custom-progress-bar {
  width: 200px;
  height: 6px;
  background: rgba(255,255,255,0.3);
  border-radius: 3px;
  overflow: hidden;
  margin-top: 8px;
}

.progress-fill {
  height: 100%;
  background: #4CAF50;
  transition: width 0.3s ease;
}
</style>

Custom Error Component

vue
<template>
  <CodeEditor v-model:value="code">
    <template #error="{ error, retry, retryable }">
      <div class="my-custom-error">
        <div class="error-icon">❌</div>
        <h3>Editor Load Failed</h3>
        <p class="error-message">{{ error.message }}</p>
        <div v-if="error.details" class="error-details">
          <details>
            <summary>View Details</summary>
            <pre>{{ error.details }}</pre>
          </details>
        </div>
        <div class="error-actions">
          <button 
            v-if="retryable" 
            @click="retry" 
            class="retry-btn"
          >
            🔄 Retry
          </button>
          <button @click="reportError" class="report-btn">
            📧 Report Issue
          </button>
        </div>
        <div v-if="error.code" class="error-code">
          Error Code: {{ error.code }}
        </div>
      </div>
    </template>
  </CodeEditor>
</template>

<script setup>
const reportError = () => {
  // Implement error reporting logic
  console.log('Reporting error...');
};
</script>

<style scoped>
.my-custom-error {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  background: #fff5f5;
  border: 2px dashed #feb2b2;
  padding: 2rem;
  text-align: center;
}

.error-icon {
  font-size: 3rem;
  margin-bottom: 1rem;
}

.error-message {
  color: #c53030;
  margin: 1rem 0;
}

.error-actions {
  display: flex;
  gap: 1rem;
  margin: 1rem 0;
}

.retry-btn, .report-btn {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-weight: 500;
}

.retry-btn {
  background: #48bb78;
  color: white;
}

.report-btn {
  background: #4299e1;
  color: white;
}

.error-code {
  font-family: monospace;
  font-size: 0.875rem;
  color: #718096;
  margin-top: 1rem;
}
</style>

Fully Disable Default Components

If you want to use only custom components and not show any default ones:

vue
<template>
  <CodeEditor 
    v-model:value="code"
    :use-default-loading="false"
    :use-default-error-boundary="false"
  >
    <template #loading="{ loading }">
      <MyCustomLoadingComponent :loading-state="loading" />
    </template>
    <template #error="{ error, retry }">
      <MyCustomErrorComponent :error="error" @retry="retry" />
    </template>
  </CodeEditor>
</template>

Slot Parameter Reference

Loading Slot Parameters

  • loading: Complete loading state object
    • stage: Current loading stage
    • progress: Loading progress (0-100)
    • loadingText: Loading text
  • loadingText: Loading text (from props or default)
  • progress: Current progress
  • showProgress: Whether to show progress

Error Slot Parameters

  • error: Error object
    • message: Error message
    • details: Error details
    • code: Error code
    • recoverable: Whether recoverable
  • retry: Retry function
  • retryable: Whether retry is allowed

Configuration Options

CodeEditor & DiffEditor Props

  • loadingText?: string - Custom loading text
  • showProgress?: boolean - Show progress bar (default true)
  • showErrorBoundary?: boolean - Show error boundary (default true)
  • retryable?: boolean - Allow retry (default true)
  • useDefaultLoading?: boolean - Use default loading component (default true)
  • useDefaultErrorBoundary?: boolean - Use default error component (default true)
  • @loading - Triggered when loading state changes
  • @error - Triggered when error state changes
  • @ready - Triggered when editor is ready

4. Utility Function Examples

Here are some useful utility functions to help you handle various Monaco Editor scenarios:

typescript
import { 
  createEditorError,
  safeAsyncExecution,
  isMonacoAvailable,
  waitForMonaco,
  validateEditorOptions 
} from 'monaco-editor-vue3';

// Create a standard error
const error = createEditorError(
  'CUSTOM_ERROR',
  'Something went wrong',
  'Additional details here'
);

// Safely execute async operations
const { success, data, error: execError } = await safeAsyncExecution(
  async () => {
    // Some async operation that may fail
    return await fetch('/api/editor-config');
  },
  'CONFIG_LOAD_ERROR',
  'Failed to load editor configuration'
);

// Check Monaco availability
if (isMonacoAvailable()) {
  console.log('Monaco is ready');
} else {
  await waitForMonaco(5000); // Wait up to 5 seconds
}

// Validate editor options
const options = { value: 'test', language: 'javascript' };
const validationError = validateEditorOptions(options);
if (validationError) {
  console.error('Invalid options:', validationError);
}

5. Best Practices

  1. Progressive enhancement: Start with default components, then customize as needed
  2. State management: Use slot parameters to get full state info
  3. Error handling: Provide user-friendly error messages and recovery options
  4. Performance: Keep custom components lightweight
  5. Accessibility: Ensure custom components support keyboard navigation and screen readers

Released under the MIT License.