React Logoreact-content-loader

react-content-loader is a lightweight React library that allows you to create customizable SVG loading placeholders (also known as skeleton screens or content loaders). Instead of showing a generic spinner or an empty white screen while data is being fetched, this library lets you display a representation of the content structure that is about to load. This technique significantly enhances the user experience by providing visual feedback, reducing perceived loading times, and making the application feel faster and more responsive.

Key features and concepts:

1. SVG-based: The loaders are generated using SVG, which makes them scalable, lightweight, and easy to customize with CSS.
2. Customizable: You can define the shapes (rectangles, circles, text blocks), their positions, dimensions, colors, and the animation speed. This allows for a high degree of customization to match your application's design.
3. Composability: You can combine multiple basic SVG elements (like `<Rect>`, `<Circle>`) within a `<ContentLoader>` component to create complex loading patterns that mimic your actual UI components (e.g., a card with an image, title, and text).
4. Theming: It supports setting `backgroundColor` and `foregroundColor` for the animation, making it easy to adapt to different themes.
5. Pre-built Components: The library offers some pre-defined loaders for common patterns like lists, cards, and bullet lists, which can be used out-of-the-box or as a starting point for further customization.
6. Accessibility: The generated SVG elements can include ARIA attributes, making the loaders more accessible.
7. Performance: Being SVG-based and designed to be minimal, it's generally performant and doesn't add significant overhead.

How it works:
When data is being loaded, you render the `ContentLoader` component. This component generates an SVG that contains shapes you've defined (e.g., a circle for an avatar, rectangles for text lines). An animation (a shimmering gradient effect by default) then passes over these shapes, creating the illusion of content being loaded. Once the actual data arrives, you replace the `ContentLoader` with your rendered components.

Installation:
`npm install react-content-loader` or `yarn add react-content-loader`

Example Code

import React, { useState, useEffect } from 'react';
import ContentLoader, { Rect, Circle } from 'react-content-loader';

// Custom Loader Component
const MyAwesomeLoader = (props) => (
  <ContentLoader
    speed={2}
    width={400}
    height={160}
    viewBox="0 0 400 160" // Defines the size of the SVG canvas
    backgroundColor="#f3f3f3"
    foregroundColor="#ecebeb"
    {...props} // Allows passing custom props like uniqueKey to ContentLoader
  >
    {/* Avatar placeholder */}
    <Circle cx="30" cy="30" r="30" />
    {/* Title placeholder */}
    <Rect x="80" y="17" rx="4" ry="4" width="300" height="13" />
    {/* Subtitle placeholder */}
    <Rect x="80" y="40" rx="3" ry="3" width="250" height="10" />
    {/* Text line placeholders */}
    <Rect x="0" y="80" rx="3" ry="3" width="400" height="10" />
    <Rect x="0" y="100" rx="3" ry="3" width="360" height="10" />
    <Rect x="0" y="120" rx="3" ry="3" width="380" height="10" />
  </ContentLoader>
);

const UserProfileCard = () => {
  const [isLoading, setIsLoading] = useState(true);
  const [userData, setUserData] = useState(null);

  useEffect(() => {
    // Simulate data fetching on component mount
    fetchUserProfile();
  }, []);

  const fetchUserProfile = () => {
    setIsLoading(true);
    setUserData(null); // Clear previous data when refreshing
    setTimeout(() => {
      setUserData({
        name: 'John Doe',
        title: 'Software Engineer',
        description: 'Passionate about building scalable web applications and contributing to open source projects. Always learning new technologies and striving for clean, efficient code.',
        avatarInitial: 'J'
      });
      setIsLoading(false);
    }, 2000); // Simulate 2 seconds loading time
  };

  return (
    <div style={{
      maxWidth: '400px', margin: '40px auto', padding: '20px',
      border: '1px solid #e0e0e0', borderRadius: '12px',
      boxShadow: '0 4px 12px rgba(0,0,0,0.05)', backgroundColor: 'white'
    }}>
      <h2 style={{ marginBottom: '25px', color: '#333' }}>User Profile</h2>

      {isLoading ? (
        // Render the loader when data is loading
        <MyAwesomeLoader />
      ) : (
        // Render actual content when data is loaded
        <div>
          <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}>
            <div style={{
              width: '60px', height: '60px', borderRadius: '50%',
              backgroundColor: '#007bff', marginRight: '20px',
              display: 'flex', justifyContent: 'center', alignItems: 'center',
              color: 'white', fontSize: '28px', fontWeight: 'bold'
            }}>
              {userData.avatarInitial}
            </div>
            <div>
              <h3 style={{ margin: '0 0 5px 0', color: '#333' }}>{userData.name}</h3>
              <p style={{ margin: '0', color: '#555', fontSize: '15px' }}>{userData.title}</p>
            </div>
          </div>
          <p style={{ color: '#444', lineHeight: '1.6', fontSize: '15px' }}>{userData.description}</p>
        </div>
      )}

      <button
        onClick={fetchUserProfile}
        style={{
          marginTop: '30px', padding: '12px 20px', backgroundColor: '#28a745',
          color: 'white', border: 'none', borderRadius: '8px',
          cursor: 'pointer', fontSize: '16px', fontWeight: 'bold',
          transition: 'background-color 0.3s ease'
        }}
        onMouseOver={(e) => e.currentTarget.style.backgroundColor = '#218838'}
        onMouseOut={(e) => e.currentTarget.style.backgroundColor = '#28a745'}
      >
        Refresh Profile
      </button>
    </div>
  );
};

export default UserProfileCard;