W kosmosie, gdy moduł stacji jest w trakcie inicjalizacji, zaloga widzi wskazniki postepu i holograficzne kontury przyszlego interfejsu. W React mamy skeleton loadery i fallback UI - elementy zastepczego interfejsu, które informują użytkownika o trwającym ladowaniu.
Dobre ładowanie to nie tylko spinner. Uzytkownik powinien miec poczucie, że cos sie dzieje i wiedziec, czego sie spodziewac.
1// 1. Nic (najgorsze) - użytkownik nie wie co sie dzieje
2<Suspense fallback={null}>
3
4// 2. Prosty tekst
5<Suspense fallback={<p>Ladowanie...</p>}>
6
7// 3. Spinner/Loader
8<Suspense fallback={<Spinner />}>
9
10// 4. Skeleton loader (najlepsze)
11<Suspense fallback={<ContentSkeleton />}>Skeleton loader to zastepczy UI, który naśladuje kształt i układ docelowej treści. Uzytkownik widzi "szkielet" strony, który stopniowo wypelnia sie prawdziwą treścią.
1function CardSkeleton() {
2 return (
3 <div className="skeleton-card">
4 <div className="skeleton-image skeleton-pulse" />
5 <div className="skeleton-title skeleton-pulse" />
6 <div className="skeleton-text skeleton-pulse" />
7 <div className="skeleton-text skeleton-pulse short" />
8 </div>
9 );
10}1.skeleton-pulse {
2 background: linear-gradient(
3 90deg,
4 #e0e0e0 25%,
5 #f0f0f0 50%,
6 #e0e0e0 75%
7 );
8 background-size: 200% 100%;
9 animation: pulse 1.5s infinite;
10 border-radius: 4px;
11}
12
13@keyframes pulse {
14 0% { background-position: 200% 0; }
15 100% { background-position: -200% 0; }
16}
17
18.skeleton-image {
19 width: 100%;
20 height: 200px;
21 margin-bottom: 12px;
22}
23
24.skeleton-title {
25 width: 60%;
26 height: 24px;
27 margin-bottom: 8px;
28}
29
30.skeleton-text {
31 width: 100%;
32 height: 16px;
33 margin-bottom: 6px;
34}
35
36.skeleton-text.short {
37 width: 40%;
38}1function ListSkeleton({ count = 3 }) {
2 return (
3 <div className="list-skeleton">
4 {Array.from({ length: count }).map((_, i) => (
5 <div key={i} className="list-item-skeleton">
6 <div className="avatar-skeleton skeleton-pulse" />
7 <div className="info-skeleton">
8 <div className="skeleton-pulse name-skeleton" />
9 <div className="skeleton-pulse detail-skeleton" />
10 </div>
11 </div>
12 ))}
13 </div>
14 );
15}1function DashboardSkeleton() {
2 return (
3 <div className="dashboard-skeleton">
4 <div className="stats-row">
5 <StatCardSkeleton />
6 <StatCardSkeleton />
7 <StatCardSkeleton />
8 </div>
9 <div className="main-content">
10 <ChartSkeleton />
11 <ListSkeleton count={5} />
12 </div>
13 </div>
14 );
15}Laduj ważne treści najpierw, reszta ładuje sie w tle:
1function MissionDashboard() {
2 return (
3 <div>
4 {/* Krytyczna tresc - bez lazy loading */}
5 <MissionStatus />
6
7 {/* Mniej krytyczna - lazy loading z skeleton */}
8 <Suspense fallback={<ChartSkeleton />}>
9 <MissionChart />
10 </Suspense>
11
12 {/* Najmniej ważna - ładuje sie ostatnia */}
13 <Suspense fallback={<ListSkeleton count={5} />}>
14 <MissionHistory />
15 </Suspense>
16 </div>
17 );
18}Nie zawsze potrzebujesz Suspense. Dla danych ladowanych asynchronicznie uzywaj stanow ładowania:
1function CrewMember({ id }) {
2 const [member, setMember] = useState(null);
3 const [loading, setLoading] = useState(true);
4
5 useEffect(() => {
6 fetchCrewMember(id)
7 .then(setMember)
8 .finally(() => setLoading(false));
9 }, [id]);
10
11 if (loading) return <MemberSkeleton />;
12
13 return (
14 <div className="crew-member">
15 <img src={member.avatar} alt={member.name} />
16 <h3>{member.name}</h3>
17 <p>{member.role}</p>
18 </div>
19 );
20}