React Loading Spinner

React Loading Spinner

In this article we'll build a React application that will display a loading spinner whenever data is being fetched from an API.

Fetch Data

Let's start by creating a hook that will simulate fetching data from an API.

// useFetch.jsx
import { useState } from 'react';

const useFetch = () => {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  setTimeout(() => {
    setData('Hello There!!!');
    setIsLoading(false);
  }, 5000);

  return { data, isLoading };
};

export default useFetch;

Global State

  1. Create a hook that returns context, or logs an error message if used outside the AppContextProvider component.

  2. Create a component that will provide state to any child component.

// AppContext.jsx
import React, { createContext, useContext } from 'react';
import Proptypes from 'prop-types';
import useFetch from './useFetch';

const AppContext = createContext();

export const useAppContext = () => {
  const context = useContext(AppContext);

  if (!context) {
    throw Error('useAppContext must be used in AppContextProvider');
  }

  return context;
};

export const AppContextProvider = ({ children }) => {
  const { data, isLoading } = useFetch();

  return (
    <AppContext.Provider value={{ data, isLoading }}>
      {children}
    </AppContext.Provider>
  );
};

AppContextProvider.propTypes = {
  children: Proptypes.node.isRequired,
};

MainPage

This component will display the data we get from useFetch. See how we're getting data from the useAppContext hook.

// MainPage.jsx
import React from 'react';
import { useAppContext } from './AppContext';

const MainPage = () => {
  const { data } = useAppContext();

  return (
    <div>{data}</div>
  );
};

export default MainPage;

Loading

We get the loading state from useAppContext, then depending on the boolean value, we'll either return a loading spinner or any children passed into the component.

// Loading.jsx
import React from 'react';
import './Loading.css';
import PropTypes from 'prop-types';
import { useAppContext } from './AppContext';

const Loading = ({ children }) => {
  const { isLoading } = useAppContext();

  return isLoading ? (
    <div className="lds-hourglass" />
  ) : (
    <>{children}</>
  );
};

Loading.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Loading;

I found this spinner css on https://loading.io/css/.

/* Loading.css */
.lds-hourglass {
  display: inline-block;
  position: relative;
  width: 80px;
  height: 80px;
}

.lds-hourglass:after {
  content: " ";
  display: block;
  border-radius: 50%;
  width: 0;
  height: 0;
  margin: 8px;
  box-sizing: border-box;
  border: 32px solid;
  border-color: #000 transparent #000 transparent;
  animation: lds-hourglass 1.2s infinite;
}

@keyframes lds-hourglass {
  0% {
    transform: rotate(0);
    animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
  }
  50% {
    transform: rotate(900deg);
    animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
  }
  100% {
    transform: rotate(1800deg);
  }
}

Put it all to use

  1. Let's wrap everything with AppContextProvider, so we'll get state to our child components.
  2. Wrap any component you want hidden when data is loading, in this case MainPage.
  3. Run your server to see the results
// App.jsx
import React from 'react';
import Loading from './Loading';
import { AppContextProvider } from './AppContext';
import MainPage from './MainPage';

const App = () => (
  <AppContextProvider>
    <Loading>
      <MainPage />
    </Loading>
  </AppContextProvider>
);

export default App;

Conclusion

We created a loading spinner component by utilizing hooks, and higher order components.

Here's a working version. Hit the reload button to see it in action.