DEV Community

William Baker
William Baker

Posted on

Building Loading Spinners with react-loader-spinner in React

react-loader-spinner is a collection of animated loading spinner components for React applications. It provides a variety of spinner styles to indicate loading states, making it easy to provide visual feedback during async operations. This guide walks through setting up and creating loading spinners using react-loader-spinner with React, from installation to a working implementation. This is part 50 of a series on using react-loader-spinner 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-loader-spinner using your preferred package manager:

npm install react-loader-spinner
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add react-loader-spinner
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add react-loader-spinner
Enter fullscreen mode Exit fullscreen mode

After installation, your package.json should include:

{
  "dependencies": {
    "react-loader-spinner": "^5.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

react-loader-spinner 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/LoaderExample.jsx:

// src/LoaderExample.jsx
import React, { useState } from 'react';
import { Oval } from 'react-loader-spinner';

function LoaderExample() {
  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' }}>
          <Oval
            height={50}
            width={50}
            color="#007bff"
            visible={true}
            ariaLabel="loading"
            secondaryColor="#cccccc"
            strokeWidth={2}
            strokeWidthSecondary={2}
          />
        </div>
      )}
    </div>
  );
}

export default LoaderExample;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

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

function App() {
  return (
    <div className="App">
      <LoaderExample />
    </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-loader-spinner provides many spinner types:

  • Oval: Oval-shaped spinner
  • ThreeDots: Three dots animation
  • TailSpin: Tail spin animation
  • Rings: Multiple rings
  • Puff: Puff animation
  • Grid: Grid pattern
  • Audio: Audio waveform
  • BallTriangle: Ball triangle animation
  • Bars: Bars animation
  • Circles: Multiple circles
  • And many more...

Key concepts:

  • Spinner Components: Each spinner type is a separate component
  • Visible Prop: Control visibility with the visible prop
  • Customization: Size, color, and animation speed can be customized
  • Accessibility: Use ariaLabel for screen readers
  • Conditional Rendering: Show/hide spinners based on loading state

Here's an example with different spinner types:

// src/MultipleSpinnersExample.jsx
import React, { useState } from 'react';
import { Oval, ThreeDots, TailSpin, Rings, Puff } from 'react-loader-spinner';

function MultipleSpinnersExample() {
  const [selectedSpinner, setSelectedSpinner] = useState('Oval');

  const spinners = {
    Oval: <Oval height={50} width={50} color="#007bff" visible={true} ariaLabel="loading" />,
    ThreeDots: <ThreeDots height={50} width={50} color="#007bff" visible={true} ariaLabel="loading" />,
    TailSpin: <TailSpin height={50} width={50} color="#007bff" visible={true} ariaLabel="loading" />,
    Rings: <Rings height={50} width={50} color="#007bff" visible={true} ariaLabel="loading" />,
    Puff: <Puff height={50} width={50} color="#007bff" visible={true} ariaLabel="loading" />
  };

  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h2>Multiple Spinner Types</h2>
      <div style={{ marginBottom: '20px' }}>
        <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 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 { Oval, ThreeDots, TailSpin, Rings } from 'react-loader-spinner';

function LoadingSystem() {
  const [loadingStates, setLoadingStates] = useState({
    button: false,
    page: false,
    inline: 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 && (
            <Oval
              height={20}
              width={20}
              color="white"
              visible={true}
              ariaLabel="loading"
            />
          )}
          {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' }}>
            <TailSpin
              height={40}
              width={40}
              color="#007bff"
              visible={true}
              ariaLabel="loading"
            />
          </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' }}>
              <Rings
                height={80}
                width={80}
                color="#007bff"
                visible={true}
                ariaLabel="loading"
              />
              <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'
          }}>
            <ThreeDots
              height={50}
              width={50}
              color="#ffffff"
              visible={true}
              ariaLabel="loading"
            />
          </div>
        )}
      </section>
    </div>
  );
}

export default LoadingSystem;
Enter fullscreen mode Exit fullscreen mode

Now create a reusable loading hook:

// src/hooks/useLoading.js
import { useState, useCallback } from 'react';

export const useLoading = (initialState = false) => {
  const [isLoading, setIsLoading] = useState(initialState);

  const startLoading = useCallback(() => {
    setIsLoading(true);
  }, []);

  const stopLoading = useCallback(() => {
    setIsLoading(false);
  }, []);

  const withLoading = useCallback(async (asyncFn) => {
    setIsLoading(true);
    try {
      const result = await asyncFn();
      return result;
    } finally {
      setIsLoading(false);
    }
  }, []);

  return {
    isLoading,
    startLoading,
    stopLoading,
    withLoading
  };
};
Enter fullscreen mode Exit fullscreen mode

Create a component using the hook:

// src/LoadingWithHook.jsx
import React from 'react';
import { useLoading } from './hooks/useLoading';
import { Oval } from 'react-loader-spinner';

function LoadingWithHook() {
  const { isLoading, withLoading } = useLoading();

  const fetchData = async () => {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    return { data: 'Loaded data' };
  };

  const handleClick = async () => {
    const result = await withLoading(fetchData);
    console.log(result);
  };

  return (
    <div style={{ padding: '20px', textAlign: 'center' }}>
      <h2>Loading with Custom Hook</h2>
      <button
        onClick={handleClick}
        disabled={isLoading}
        style={{
          padding: '10px 20px',
          backgroundColor: '#007bff',
          color: 'white',
          border: 'none',
          borderRadius: '4px',
          cursor: isLoading ? 'not-allowed' : 'pointer',
          marginBottom: '20px'
        }}
      >
        {isLoading ? 'Loading...' : 'Fetch Data'}
      </button>
      {isLoading && (
        <div style={{ display: 'flex', justifyContent: 'center' }}>
          <Oval
            height={50}
            width={50}
            color="#007bff"
            visible={true}
            ariaLabel="loading"
          />
        </div>
      )}
    </div>
  );
}

export default LoadingWithHook;
Enter fullscreen mode Exit fullscreen mode

Update your App.jsx:

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

function App() {
  return (
    <div className="App">
      <LoadingSystem />
      <LoadingWithHook />
    </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
  • Custom loading hook
  • Async operation handling

Common Issues / Troubleshooting

  1. Spinner not displaying: Make sure the visible prop is set to true. Also check that the component is being rendered (not conditionally hidden).

  2. Spinner too small or large: Adjust the height and width props to control the spinner size. These values are 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. Spinner not animating: Ensure the visible prop is true. The spinner only animates when visible.

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

  6. Accessibility: Always provide an ariaLabel prop for screen readers. This helps users with assistive technologies understand what's loading.

Next Steps

Now that you have an understanding of react-loader-spinner:

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

Summary

You've successfully set up react-loader-spinner in your React application and created loading indicators for buttons, inline content, page loading, and overlays. react-loader-spinner provides a variety of spinner types to indicate loading states with minimal configuration.

SEO Keywords

react-loader-spinner
React loading spinner
react-loader-spinner tutorial
React loading indicator
react-loader-spinner installation
React spinner component
react-loader-spinner example
React async loading
react-loader-spinner setup
React loading states
react-loader-spinner customization
React spinner types
react-loader-spinner hooks
React loading library
react-loader-spinner getting started

Top comments (0)