Basic Loading Scenarios
Lumidot makes it easy to add beautiful loading animations to your React applications. Here are the most common scenarios.Page Loading
Show a centered loading animation while your page content loads.import { Lumidot } from 'lumidot';
import { Suspense } from 'react';
function PageLoader() {
return (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
minHeight: '100vh'
}}>
<Lumidot
variant="blue"
pattern="all"
rows={3}
cols={3}
scale={2}
glow={8}
/>
</div>
);
}
export default function App() {
return (
<Suspense fallback={<PageLoader />}>
{/* Your page content */}
</Suspense>
);
}
Data Fetching
Display a loading animation while fetching data from an API.import { Lumidot } from 'lumidot';
import { useState, useEffect } from 'react';
function UserList() {
const [users, setUsers] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
});
}, []);
if (loading) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<Lumidot
variant="emerald"
pattern="wave-lr"
rows={3}
cols={5}
scale={1.5}
/>
<p style={{ marginTop: '1rem', color: '#666' }}>Loading users...</p>
</div>
);
}
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
Form Submission
Show feedback during form submission with a compact loader.import { Lumidot } from 'lumidot';
import { useState } from 'react';
function ContactForm() {
const [submitting, setSubmitting] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
setSubmitting(true);
await fetch('/api/contact', {
method: 'POST',
body: new FormData(e.target)
});
setSubmitting(false);
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Message" />
<button type="submit" disabled={submitting}>
{submitting ? (
<span style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
<Lumidot
variant="white"
pattern="line-h-mid"
rows={1}
cols={5}
scale={0.8}
glow={4}
/>
Sending...
</span>
) : (
'Send Message'
)}
</button>
</form>
);
}
Button Loading State
Inline loading animation for buttons.import { Lumidot } from 'lumidot';
import { useState } from 'react';
function SaveButton({ onSave }) {
const [saving, setSaving] = useState(false);
const handleClick = async () => {
setSaving(true);
await onSave();
setSaving(false);
};
return (
<button
onClick={handleClick}
disabled={saving}
style={{
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.5rem 1rem'
}}
>
{saving && (
<Lumidot
variant="white"
pattern="duo-h"
rows={1}
cols={3}
scale={0.7}
glow={6}
/>
)}
{saving ? 'Saving...' : 'Save Changes'}
</button>
);
}
Card Loading Placeholder
Show a skeleton loader for card components.import { Lumidot } from 'lumidot';
function CardSkeleton() {
return (
<div style={{
border: '1px solid #e5e7eb',
borderRadius: '8px',
padding: '1.5rem',
backgroundColor: '#f9fafb'
}}>
<div style={{ display: 'flex', justifyContent: 'center', padding: '2rem 0' }}>
<Lumidot
variant="slate"
pattern="spiral"
rows={4}
cols={4}
scale={1.2}
glow={6}
duration={1}
/>
</div>
</div>
);
}
function ProductCard({ id }) {
const [product, setProduct] = useState(null);
useEffect(() => {
fetch(`/api/products/${id}`)
.then(res => res.json())
.then(setProduct);
}, [id]);
if (!product) return <CardSkeleton />;
return (
<div style={{
border: '1px solid #e5e7eb',
borderRadius: '8px',
padding: '1.5rem'
}}>
<h3>{product.name}</h3>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}
Infinite Scroll Loading
Show a loader at the bottom of a list for infinite scroll.import { Lumidot } from 'lumidot';
import { useEffect, useRef } from 'react';
function InfiniteList() {
const [items, setItems] = useState([]);
const [loading, setLoading] = useState(false);
const [page, setPage] = useState(1);
const observerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && !loading) {
setLoading(true);
fetch(`/api/items?page=${page}`)
.then(res => res.json())
.then(data => {
setItems(prev => [...prev, ...data]);
setPage(prev => prev + 1);
setLoading(false);
});
}
},
{ threshold: 0.1 }
);
if (observerRef.current) {
observer.observe(observerRef.current);
}
return () => observer.disconnect();
}, [page, loading]);
return (
<div>
{items.map((item, i) => (
<div key={i}>{item.name}</div>
))}
<div ref={observerRef} style={{ padding: '2rem', textAlign: 'center' }}>
{loading && (
<Lumidot
variant="indigo"
pattern="wave-tb"
rows={5}
cols={3}
scale={1}
/>
)}
</div>
</div>
);
}
Tips
- Use
pattern="all"orpattern="wave-lr"for general loading states - Use
pattern="line-h-mid"orpattern="duo-h"for inline button loaders - Use
pattern="spiral"orpattern="frame"for card placeholders - Adjust
scaleto match your component size - Match
variantto your design system colors