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
Create a hook that returns context, or logs an error message if used outside the AppContextProvider component.
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
- Let's wrap everything with AppContextProvider, so we'll get state to our child components.
- Wrap any component you want hidden when data is loading, in this case MainPage.
- 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.