DEV Community

Joseph Nelson
Joseph Nelson

Posted on

Building Loading Spinners with react-spinners in React

react-spinners is a collection of animated loading spinner components for React applications. It provides a variety of spinner styles with customizable colors, sizes, and speeds, making it easy to add professional loading indicators to your app. This guide walks through setting up and creating loading spinners using react-spinners with React, from installation to a working implementation. This is part 53 of a series on using react-spinners with React.

Prerequisites

Before you begin, make sure you have:

  • Node.js version 14.0 or higher installed
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) or create-react-app setup
  • Basic knowledge of React hooks (useState, useEffect)
  • Familiarity with JavaScript/TypeScript
  • Understanding of async operations

Installation

Install react-spinners using your preferred package manager:

npm install react-spinners
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-spinners
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-spinners
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-spinners": "^0.13.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

react-spinners requires no additional setup. Import the spinner components and you're ready to use them.

First Example / Basic Usage

Let's create a simple loading spinner. Create a new file src/SpinnerExample.jsx:

// src/SpinnerExample.jsx
import React, { useState } from 'react';
import { ClipLoader } from 'react-spinners';

function SpinnerExample() {
  const [isLoading, setIsLoading] = useState(false);

  const handleLoad = async () => {
    setIsLoading(true);
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    setIsLoading(false);
  };

  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h2>Loading Spinner Example</h2>
      <button
        onClick={handleLoad}
        disabled={isLoading}
        style={{
          padding: '10px 20px',
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: isLoading ? 'not-allowed' : 'pointer',
          marginBottom: '20px'
        }}
      >
        {isLoading ? 'Loading...' : 'Start Loading'}
      </button>
      {isLoading && (
        <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
          <ClipLoader color="#007bff" size={50} />
        </div>
      )}
    </div>
  );
}

export default SpinnerExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import SpinnerExample from './SpinnerExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <SpinnerExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This creates a loading spinner that appears when the button is clicked and disappears after the async operation completes.

Understanding the Basics

react-spinners provides many spinner types:

  • ClipLoader: Clip animation
  • BeatLoader: Beat animation
  • BounceLoader: Bounce animation
  • CircleLoader: Circle animation
  • ClimbingBoxLoader: Climbing box animation
  • DotLoader: Dot animation
  • FadeLoader: Fade animation
  • GridLoader: Grid animation
  • HashLoader: Hash animation
  • MoonLoader: Moon animation
  • PacmanLoader: Pacman animation
  • PropagateLoader: Propagate animation
  • PuffLoader: Puff animation
  • PulseLoader: Pulse animation
  • RingLoader: Ring animation
  • RiseLoader: Rise animation
  • RotateLoader: Rotate animation
  • ScaleLoader: Scale animation
  • SyncLoader: Sync animation
  • And many more...

Key concepts:

  • Spinner Components: Each spinner type is a separate component
  • Color Prop: Customize spinner color
  • Size Prop: Control spinner size
  • Loading Prop: Control visibility with loading prop
  • SpeedMultiplier: Adjust animation speed
  • CSS Override: Customize with cssOverride prop

Here's an example with different spinner types and customization:

// src/MultipleSpinnersExample.jsx
import React, { useState } from 'react';
import { ClipLoader, BeatLoader, BounceLoader, CircleLoader, HashLoader } from 'react-spinners';

function MultipleSpinnersExample() {
  const [selectedSpinner, setSelectedSpinner] = useState('ClipLoader');
  const [color, setColor] = useState('#007bff');
  const [size, setSize] = useState(50);
  const [speed, setSpeed] = useState(1);

  const spinners = {
    ClipLoader: <ClipLoader color={color} size={size} speedMultiplier={speed} />,
    BeatLoader: <BeatLoader color={color} size={size} speedMultiplier={speed} />,
    BounceLoader: <BounceLoader color={color} size={size} speedMultiplier={speed} />,
    CircleLoader: <CircleLoader color={color} size={size} speedMultiplier={speed} />,
    HashLoader: <HashLoader color={color} size={size} speedMultiplier={speed} />
  };

  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h2>Multiple Spinner Types</h2>
      <div style={{ marginBottom: '20px', display: 'flex', flexDirection: 'column', gap: '10px', maxWidth: '300px', margin: '0 auto 20px' }}>
        <div>
          <label>Select Spinner: </label>
          <select
            value={selectedSpinner}
            onChange={(e) => setSelectedSpinner(e.target.value)}
            style={{ padding: '5px', marginLeft: '10px' }}
          >
            {Object.keys(spinners).map(type => (
              <option key={type} value={type}>{type}</option>
            ))}
          </select>
        </div>
        <div>
          <label>Color: </label>
          <input
            type="color"
            value={color}
            onChange={(e) => setColor(e.target.value)}
            style={{ marginLeft: '10px' }}
          />
        </div>
        <div>
          <label>Size: </label>
          <input
            type="range"
            min="20"
            max="100"
            value={size}
            onChange={(e) => setSize(Number(e.target.value))}
            style={{ marginLeft: '10px' }}
          />
          <span style={{ marginLeft: '10px' }}>{size}px</span>
        </div>
        <div>
          <label>Speed: </label>
          <input
            type="range"
            min="0.5"
            max="2"
            step="0.1"
            value={speed}
            onChange={(e) => setSpeed(Number(e.target.value))}
            style={{ marginLeft: '10px' }}
          />
          <span style={{ marginLeft: '10px' }}>{speed}x</span>
        </div>
      </div>
      <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
        {spinners[selectedSpinner]}
      </div>
    </div>
  );
}

export default MultipleSpinnersExample;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive loading system with different spinner types and use cases:

// src/LoadingSystem.jsx
import React, { useState } from 'react';
import { ClipLoader, BeatLoader, HashLoader, PulseLoader, ScaleLoader } from 'react-spinners';

function LoadingSystem() {
  const [loadingStates, setLoadingStates] = useState({
    button: false,
    inline: false,
    page: false,
    overlay: false
  });

  const simulateApiCall = async (type) => {
    setLoadingStates(prev => ({ ...prev, [type]: true }));
    await new Promise(resolve => setTimeout(resolve, 2000));
    setLoadingStates(prev => ({ ...prev, [type]: false }));
  };

  return (
    <div style={{ padding: '20px' }}>
      <h1>Loading System Examples</h1>

      {/* Button Loading */}
      <section style={{ marginBottom: '40px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Button Loading</h2>
        <button
          onClick={() => simulateApiCall('button')}
          disabled={loadingStates.button}
          style={{
            padding: '10px 20px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: loadingStates.button ? 'not-allowed' : 'pointer',
            display: 'flex',
            alignItems: 'center',
            gap: '10px'
          }}
        >
          {loadingStates.button && <ClipLoader color="white" size={20} />}
          {loadingStates.button ? 'Loading...' : 'Submit'}
        </button>
      </section>

      {/* Inline Loading */}
      <section style={{ marginBottom: '40px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Inline Loading</h2>
        <button
          onClick={() => simulateApiCall('inline')}
          style={{
            padding: '10px 20px',
            backgroundColor: '#28a745',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            marginBottom: '20px'
          }}
        >
          Load Data
        </button>
        {loadingStates.inline ? (
          <div style={{ display: 'flex', justifyContent: 'center', padding: '20px' }}>
            <HashLoader color="#007bff" size={40} />
          </div>
        ) : (
          <div style={{ padding: '20px', backgroundColor: '#f8f9fa', borderRadius: '4px' }}>
            <p>Data loaded successfully!</p>
          </div>
        )}
      </section>

      {/* Page Loading */}
      <section style={{ marginBottom: '40px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px' }}>
        <h2>Page Loading</h2>
        <button
          onClick={() => simulateApiCall('page')}
          style={{
            padding: '10px 20px',
            backgroundColor: '#ffc107',
            color: 'black',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Load Page Content
        </button>
        {loadingStates.page && (
          <div style={{
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: 'rgba(255, 255, 255, 0.9)',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            zIndex: 1000
          }}>
            <div style={{ textAlign: 'center' }}>
              <PulseLoader color="#007bff" size={15} />
              <p style={{ marginTop: '20px', fontSize: '18px' }}>Loading page content...</p>
            </div>
          </div>
        )}
      </section>

      {/* Overlay Loading */}
      <section style={{ marginBottom: '40px', padding: '20px', border: '1px solid #ddd', borderRadius: '8px', position: 'relative' }}>
        <h2>Overlay Loading</h2>
        <button
          onClick={() => simulateApiCall('overlay')}
          style={{
            padding: '10px 20px',
            backgroundColor: '#dc3545',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Process Data
        </button>
        <div style={{
          padding: '20px',
          backgroundColor: '#f8f9fa',
          borderRadius: '4px',
          marginTop: '20px',
          minHeight: '200px'
        }}>
          <p>Content area that can be overlaid with a loading spinner.</p>
        </div>
        {loadingStates.overlay && (
          <div style={{
            position: 'absolute',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: 'rgba(0, 0, 0, 0.5)',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            borderRadius: '8px'
          }}>
            <ScaleLoader color="#ffffff" />
          </div>
        )}
      </section>
    </div>
  );
}

export default LoadingSystem;
Enter fullscreen mode Exit fullscreen mode

Now create a reusable loading component with CSS override:

// src/components/LoadingIndicator.jsx
import React from 'react';
import { ClipLoader } from 'react-spinners';

function LoadingIndicator({ size = 50, color = '#007bff', fullScreen = false, cssOverride = {} }) {
  const defaultCssOverride = {
    display: 'block',
    margin: '0 auto',
    ...cssOverride
  };

  if (fullScreen) {
    return (
      <div style={{
        position: 'fixed',
        top: 0,
        left: 0,
        right: 0,
        bottom: 0,
        backgroundColor: 'rgba(255, 255, 255, 0.9)',
        display: 'flex',
        justifyContent: 'center',
        alignItems: 'center',
        zIndex: 1000
      }}>
        <ClipLoader color={color} size={size} cssOverride={defaultCssOverride} />
      </div>
    );
  }

  return (
    <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', padding: '20px' }}>
      <ClipLoader color={color} size={size} cssOverride={defaultCssOverride} />
    </div>
  );
}

export default LoadingIndicator;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

// src/App.jsx
import React from 'react';
import LoadingSystem from './LoadingSystem';
import MultipleSpinnersExample from './MultipleSpinnersExample';
import './App.css';

function App() {
  return (
    <div className="App">
      <LoadingSystem />
      <MultipleSpinnersExample />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

This example demonstrates:

  • Multiple spinner types
  • Button loading states
  • Inline loading indicators
  • Page-level loading
  • Overlay loading
  • Customizable colors, sizes, and speeds
  • CSS override for advanced styling

Common Issues / Troubleshooting

  1. Spinner not displaying: Make sure the loading prop is set to true (if using it) or the component is conditionally rendered. Also check that you're importing the correct spinner component.

  2. Spinner too small or large: Adjust the size prop to control the spinner size. The size is in pixels.

  3. Color not changing: Use the color prop to set the spinner color. It accepts any valid CSS color value (hex, rgb, named colors).

  4. Animation too fast or slow: Use the speedMultiplier prop to adjust animation speed. Values less than 1 slow down, values greater than 1 speed up.

  5. Styling conflicts: Use the cssOverride prop to override default styles. This is useful when you need to customize positioning or other CSS properties.

  6. Multiple spinners: You can use multiple spinner instances on the same page. Each spinner manages its own state independently.

Next Steps

Now that you have an understanding of react-spinners:

  • Explore all available spinner types
  • Learn about advanced customization with cssOverride
  • Implement loading states in forms and data fetching
  • Add loading indicators to async operations
  • Create custom loading components
  • Learn about other loading libraries (react-loader-spinner, react-spinners-css)
  • Check the official repository: https://github.com/davidhu2000/react-spinners
  • Look for part 54 of this series for more advanced topics

Summary

You've successfully set up react-spinners in your React application and created loading indicators for buttons, inline content, page loading, and overlays. react-spinners provides a variety of spinner types with extensive customization options for professional loading indicators.

SEO Keywords

react-spinners
React loading spinner
react-spinners tutorial
React spinner component
react-spinners installation
React loading indicator
react-spinners example
React animated spinner
react-spinners setup
React spinner library
react-spinners customization
React spinner types
react-spinners cssOverride
React loading library
react-spinners getting started

Top comments (0)