React - Loading Images from Network
Overview
Estimated time: 20–30 minutes
Load images efficiently with lazy-loading, skeleton placeholders, and error fallbacks.
Try it: Lazy image list
View source
function LazyImg({src, alt}){
const ref = React.useRef(null);
const [state, setState] = React.useState('idle'); // idle|loading|loaded|error
const [inView, setInView] = React.useState(false);
React.useEffect(() => {
const el = ref.current; if (!el) return;
const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) setInView(true); }, {rootMargin:'200px'});
io.observe(el); return () => io.disconnect();
}, []);
return (
<div ref={ref} style={{height:160, position:'relative', background:'#eee', display:'grid', placeItems:'center'}}>
{state!=='loaded' && <div className="skeleton" style={{position:'absolute', inset:0, animation:'pulse 1.2s infinite'}}></div>}
{inView && <img alt={alt} src={src} onLoad={() => setState('loaded')} onError={() => setState('error')} style={{width:'100%', height:'100%', objectFit:'cover', display: state==='loaded'? 'block':'none'}} />}
{state==='error' && <div>⚠️ Failed to load</div>}
</div>
);
}
function App(){
const imgs = Array.from({length:20}, (_,i)=> `https://picsum.photos/seed/${i+1}/600/400`);
return <div style={{display:'grid', gap:12}}>{imgs.map((src,i)=> <LazyImg key={i} src={src} alt={`Img ${i+1}`} />)}</div>;
}
ReactDOM.createRoot(document.getElementById('try-imgs')).render(<App />);
Syntax primer
- IntersectionObserver triggers loading when images near the viewport.
- Use skeletons to reduce perceived load time.
Common pitfalls
- Not reserving space for images—causes layout shift.
Exercises
- Swap to native
loading="lazy"
and compare behavior. - Add retry button on error.
Checks for Understanding
- What benefit do skeletons provide compared to spinners?
- How do you avoid layout shift when images load?
Show answers
- They reserve space and convey structure, reducing perceived wait time.
- Reserve dimensions with a fixed box or aspect-ratio container.