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.