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:
- Immediate loading: Starts loading the component in the constructor
- State management: Tracks loading status internally
- Automatic rendering: Switches between loading, loaded, and error states
- 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
- Match loading UI to content: Use skeleton screens for complex layouts, simple spinners for quick loads
- Handle errors gracefully: Always provide error UI with retry options
- Cleanup intervals: Remember to clear intervals in
beforeUnmount()
- Consider timing: Show loading state immediately for loads over 200ms
- Test loading states: Use
createDelayedComponent() to test loading UI during development