Skip to main content
While GlyphUI doesn’t provide a separate Suspense component like React, the lazy() function includes built-in suspense-like behavior for handling loading states.

Understanding Suspense in GlyphUI

In GlyphUI, suspense behavior is built directly into the lazy() function. When a component is being loaded asynchronously, the lazy wrapper automatically handles the loading state and displays fallback UI.

How It Works

The lazy() function creates a wrapper component that manages three states:
{
  status: 'loading',  // or 'loaded' or 'error'
  Component: null,    // The loaded component class
  error: null        // Error object if loading failed
}
Based on the status, it renders:
  • Loading state: Shows the loading component or default “Loading…” text
  • Loaded state: Renders the actual component
  • Error state: Displays the error component or default error message

Loading States

You can customize the loading UI by passing a loading option to lazy():
import { lazy, h } from 'glyphui';

// Using a simple vdom element
const LazyPage = lazy(
  () => import('./MyPage.js'),
  {
    loading: h('div', { style: { padding: '20px' } }, ['Loading...'])
  }
);

// Or using a component class
class LoadingSpinner extends Component {
  render() {
    return h('div', { class: 'spinner' }, [
      h('div', { class: 'spinner-icon' })
    ]);
  }
}

const LazyDashboard = lazy(
  () => import('./Dashboard.js'),
  { loading: LoadingSpinner }
);

Default Loading Component

If you don’t provide a custom loading component, lazy() uses this default:
const defaultLoading = () => h('div', {
  style: {
    padding: '20px',
    textAlign: 'center'
  }
}, ['Loading...']);

Animated Loading Component

Create an engaging loading experience with animations:
class AnimatedLoader extends Component {
  constructor(props) {
    super(props, {
      initialState: { dots: 1 }
    });
    
    // Animate the dots
    this.interval = setInterval(() => {
      this.setState({ dots: (this.state.dots % 3) + 1 });
    }, 300);
  }
  
  beforeUnmount() {
    clearInterval(this.interval);
  }
  
  render() {
    const dotsStr = '.'.repeat(this.state.dots);
    
    return h('div', {
      style: {
        padding: '30px',
        textAlign: 'center',
        background: '#f9f9f9',
        borderRadius: '4px'
      }
    }, [
      h('div', {
        style: { fontSize: '20px', marginBottom: '10px' }
      }, [`Loading${dotsStr}`]),
      h('div', {
        style: {
          height: '4px',
          width: '100%',
          backgroundColor: '#eee',
          borderRadius: '2px',
          overflow: 'hidden'
        }
      }, [
        h('div', {
          style: {
            height: '100%',
            width: '33%',
            backgroundColor: '#4CAF50',
            animation: 'progress 1s infinite'
          }
        })
      ])
    ]);
  }
}

const LazyProfile = lazy(
  () => import('./Profile.js'),
  { loading: AnimatedLoader }
);

Error Handling

Customize how errors are displayed when component loading fails:
class ErrorDisplay extends Component {
  render(props) {
    const { error } = props;
    
    return h('div', {
      style: {
        padding: '20px',
        color: '#d32f2f',
        backgroundColor: '#ffebee',
        borderRadius: '4px',
        border: '1px solid #ef5350'
      }
    }, [
      h('h3', {}, ['Failed to Load Component']),
      h('p', {}, [error.message]),
      h('pre', {
        style: {
          background: '#fff',
          padding: '10px',
          fontSize: '12px',
          overflow: 'auto'
        }
      }, [error.stack || 'No stack trace available']),
      h('button', {
        on: { click: () => window.location.reload() },
        style: {
          marginTop: '10px',
          padding: '8px 16px',
          cursor: 'pointer'
        }
      }, ['Reload Page'])
    ]);
  }
}

const LazySettings = lazy(
  () => import('./Settings.js'),
  { error: ErrorDisplay }
);

Default Error Component

If no custom error component is provided, lazy() uses this default:
const defaultError = (error) => h('div', {
  style: {
    padding: '20px',
    color: 'red'
  }
}, [
  h('p', {}, ['Error loading component:']),
  h('pre', {
    style: {
      background: '#f5f5f5',
      padding: '10px'
    }
  }, [error.message])
]);

Complete Example

Here’s a full example showing loading states with lazy():
import {
  Component,
  h,
  lazy,
  createDelayedComponent
} from 'glyphui';

// Loading component
class Spinner extends Component {
  render() {
    return h('div', {
      class: 'loading-container',
      style: {
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        height: '200px'
      }
    }, [
      h('div', { class: 'spinner' }, ['Loading...'])
    ]);
  }
}

// Error component
class ErrorBoundary extends Component {
  render(props) {
    return h('div', {
      class: 'error-container',
      style: {
        padding: '20px',
        backgroundColor: '#ffebee',
        borderRadius: '4px'
      }
    }, [
      h('h3', {}, ['Something went wrong']),
      h('p', {}, [props.error.message])
    ]);
  }
}

// The actual page component
class DashboardPage extends Component {
  render() {
    return h('div', { class: 'dashboard' }, [
      h('h1', {}, ['Dashboard']),
      h('p', {}, ['Welcome to your dashboard!'])
    ]);
  }
}

// Create lazy component with custom loading and error handling
const LazyDashboard = lazy(
  createDelayedComponent(DashboardPage, 2000),
  {
    loading: Spinner,
    error: ErrorBoundary
  }
);

// Use in your app
class App extends Component {
  render() {
    return h('div', {}, [
      h('h1', {}, ['My App']),
      LazyDashboard()
    ]);
  }
}

Fallback UI Patterns

Skeleton Screens

Provide a structural preview of the content being loaded:
class SkeletonLoader extends Component {
  render() {
    return h('div', { class: 'skeleton' }, [
      h('div', {
        class: 'skeleton-header',
        style: {
          height: '40px',
          backgroundColor: '#e0e0e0',
          marginBottom: '20px',
          borderRadius: '4px'
        }
      }),
      h('div', {
        class: 'skeleton-line',
        style: {
          height: '20px',
          backgroundColor: '#e0e0e0',
          marginBottom: '10px',
          borderRadius: '4px',
          width: '80%'
        }
      }),
      h('div', {
        class: 'skeleton-line',
        style: {
          height: '20px',
          backgroundColor: '#e0e0e0',
          marginBottom: '10px',
          borderRadius: '4px',
          width: '60%'
        }
      })
    ]);
  }
}

Progress Indicators

Show loading progress for a better user experience:
class ProgressLoader extends Component {
  constructor(props) {
    super(props, {
      initialState: { progress: 0 }
    });
    
    this.interval = setInterval(() => {
      if (this.state.progress < 90) {
        this.setState({ progress: this.state.progress + 10 });
      }
    }, 200);
  }
  
  beforeUnmount() {
    clearInterval(this.interval);
  }
  
  render() {
    return h('div', { style: { padding: '20px' } }, [
      h('div', { style: { marginBottom: '10px' } }, [
        `Loading... ${this.state.progress}%`
      ]),
      h('div', {
        style: {
          height: '4px',
          backgroundColor: '#e0e0e0',
          borderRadius: '2px',
          overflow: 'hidden'
        }
      }, [
        h('div', {
          style: {
            height: '100%',
            width: `${this.state.progress}%`,
            backgroundColor: '#4CAF50',
            transition: 'width 0.3s ease'
          }
        })
      ])
    ]);
  }
}
Use skeleton screens for content-heavy pages and simple spinners for quick interactions. Match your loading UI to the content being loaded for a seamless experience.

How Lazy Works with Suspense Behavior

The LazyComponent wrapper implements suspense-like behavior:
  1. Immediate loading: Starts loading the component in the constructor
  2. State management: Tracks loading status internally
  3. Automatic rendering: Switches between loading, loaded, and error states
  4. Props forwarding: Passes all props to the loaded component
From lazy.js:63:
render(props, state) {
  const { status, Component, error } = state;
  
  switch (status) {
    case 'loading':
      return typeof loadingComponent === 'function'
        ? createComponent(loadingComponent, {})
        : loadingComponent;
        
    case 'loaded':
      return createComponent(Component, props);
      
    case 'error':
      return typeof errorComponent === 'function'
        ? createComponent(errorComponent, { error })
        : errorComponent;
  }
}
Always provide meaningful loading states. A blank screen during loading creates a poor user experience and may make users think the app has frozen.

Best Practices

  1. Match loading UI to content: Use skeleton screens for complex layouts, simple spinners for quick loads
  2. Handle errors gracefully: Always provide error UI with retry options
  3. Cleanup intervals: Remember to clear intervals in beforeUnmount()
  4. Consider timing: Show loading state immediately for loads over 200ms
  5. Test loading states: Use createDelayedComponent() to test loading UI during development

Build docs developers (and LLMs) love