Skeleton Loaders: Simplifying Data Loading in React: Part 1
Part One: Creating Our React App
Welcome to our two-part series on Skeleton Loaders: Simplifying Data Loading in React! In part one of this series, we'll create our modern react application from scratch, fetch the data, and add styling.
Understanding the Power of Skeleton Loading Screens
Many modern websites handle data fetching in the browser instead of on the server. This is good because it means that a user doesn’t have to wait long before getting their content loaded from the server but then has to wait for their data to be loaded from the browser once it arrives.
Developers often use loaders or spinners to manage user expectations during this data-fetching process. A particularly effective and popular approach, adopted by major websites like Facebook and LinkedIn, is the use of skeleton loading screens. These screens display placeholder elements that mimic the layout of the actual content, providing a visual representation of the incoming data.
Prerequisites
Basic knowledge of React.
Familiarity with React Hooks.
Setting Up the Project
First, we'll create a new React application using the following command:
npx create-react-app react-skeleton-screens
Next, navigate into the newly created project directory:
cd react-skeleton-screens
Open the project in Visual Studio Code:
code .
Removing Boilerplate Code
Let's clean up the default files created by create-react-app
:
Open the
src
folder and delete the following files:App.css
App.test.js
logo.svg
setupTests.js
In
index.js
, remove the import and invocation of the service worker.In
App.js
, remove the import oflogo.svg
andApp.css
.
Replace the code in App.js
with the following:
import React from 'react';
import User from './components/User';
import Articles from './components/Articles';
function App() {
return (
<div className="App">
<header>
<h1>React Skeletons</h1>
</header>
<div className="content">
</div>
</div>
);
}
export default App;
Creating Components
Now, let's create the necessary components. In the src
directory, create a new folder called components
. Inside this folder, create one file: Home.jsx
.
import React from 'react';
const Home = () => {
return (
<div className="home">
</div>
);
}
export default Home;
With these steps, you've set up a basic React application structure and created components for our landing page, which will serve as the foundation for implementing skeleton screens. This setup ensures a clean slate, allowing you to focus on building the skeleton loading screens that enhance user experience during data fetching.
Now we’re going to nest the Home component inside the content div in our App.js component. Copy the code below into your app.js to do this:
import Home from './Home';
function App() {
return (
<div className="App">
<header>
<h1>Meal Recipes</h1>
</header>
<div className="content">
<Home />
</div>
</div>
);
}
export default App;
Adding Styles to the Application
To enhance the appearance of our application, we'll apply some styles to the header in App.js
. Follow the steps below to update the styles in your index.css
file.
Update index.css
Open index.css
and replace its contents with the following styles:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
header {
font-size: 1.5rem;
font-weight: 900;
display: grid;
align-items: center;
}
header h1 {
max-width: 1200px;
margin: 0 auto;
}
.container {
background-color: #6b7280;
color: #ffffff;
min-height: 100vh;
transition-property: all;
transition-duration: 1s;
transition-timing-function: ease-out;
}
.meals {
display: grid;
grid-template-columns: repeat(1, minmax(0, 1fr));
gap: 1.25rem; /* Equivalent to gap-5 in Tailwind */
margin-top: 1.25rem; /* Equivalent to mt-5 in Tailwind */
transition-property: all;
transition-duration: 1s;
transition-timing-function: ease-out;
padding: 10px 50px;
}
@media (min-width: 640px) {
.meals {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (min-width: 768px) {
.meals {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (min-width: 1280px) {
.meals {
grid-template-columns: repeat(4, minmax(0, 1fr));
}
}
/* .meal class */
.meal {
/* Equivalent to rounded overflow-hidden shadow-md cursor-pointer relative h-60 w-60 xs:h-56 xs:w-52 sm:h-56 sm:w-52 lg:h-56 lg:w-52 text-left; */
border-radius: 0.25rem;
overflow: hidden;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
cursor: pointer;
position: relative;
height: 15rem; /* Equivalent to h-60 */
width: 15rem; /* Equivalent to w-60 */
}
.meal-img:hover {
box-shadow: 0 10px 15px -3px rgba(147, 102, 102, 0.1),
0 4px 6px -2px rgba(0, 0, 0, 0.05);
transition-property: all;
transition-duration: 1s;
transition-timing-function: ease-out;
}
/* .meal-img class */
.meal-img {
/* Equivalent to w-full h-full border-solid border-4 border-white; */
width: 100%;
height: 100%;
border-style: solid;
border-width: 4px;
border-color: #ffffff; /* Assuming white border */
}
Explanation of Styles
The universal selector (
*
) is used to apply the following styles to all elements on the page:margin: 0;
andpadding: 0;
set the margin and padding of all elements to zero, effectively removing any default spacing.box-sizing: border-box;
ensures that the total width and height of an element include both its content and padding, but not the margin.
The
header
element:font-size: 1.5rem;
sets the font size to 1.5 times the default font size.font-weight: 900;
makes the text bold.display: grid;
uses CSS Grid for layout.align-items: center;
vertically centers the content within the header.
The
.container
class:background-color: #6b7280;
sets the background color to a shade of gray (#6b7280).color: #ffffff;
sets the text color to white.min-height: 100vh;
ensures that the container takes up at least the full viewport height.The
transition
properties control the animation duration and timing function when transitioning styles.
The
.meals
class:Uses CSS Grid to create a responsive grid layout with varying column numbers based on screen width.
gap: 1.25rem;
adds spacing between grid items.margin-top: 1.25rem;
provides top margin.The
transition
properties control the animation when transitioning styles.padding: 10px 50px;
adds padding to the container.
Media queries:
- Adjust the number of columns in the
.meals
grid based on screen width.
- Adjust the number of columns in the
The
.meal
class:Creates a card-like styling for meal items.
Sets border radius, overflow behavior, box shadow, cursor, and dimensions.
The
.meal-img
class:- Sets the image dimensions and adds a white border.
These styles will ensure that your application's header looks clean and visually appealing. The header will have a consistent blue background with white text, providing a professional look.
Final Steps
After adding the styles, your header in the application should now have a distinct appearance, with a blue background and centered white text.
Running the Application
To see your changes in action, make sure to start your development server if it's not already running:
yarn dev
Navigate to http://localhost:3000
in your browser to view the updated header with the new styles applied.
With these styling updates, your React application now has a polished header, setting a solid foundation for building out the rest of the skeleton loading screens and other features.
Fetching Data
We're going to use the MealDB API for our data fetching: https://www.themealdb.com/api.php.
In our App.js
, let's create a state to store our data when we fetch it.
const [meals, setMeals] = useState(null);
Initially, our meals
state will be null
because we don't have any meal data yet. However, when this component is rendered to the DOM, we need to fetch the data. To do this, we'll use the useEffect
hook, which runs automatically after the component has been rendered.
Let's create a useEffect
and use a setTimeout
so that we can see the effect of the skeleton loader for a bit longer. Note that we wouldn't typically use a delay like this in a production application. Copy and paste the code below into the App.js
file:
import React, { useState, useEffect } from 'react';
import Home from './components/Home';
function App() {
const [meals, setMeals] = useState(null);
// runs automatically after initial render
useEffect(() => {
setTimeout( async () => {
const res = await fetch('https://www.themealdb.com/api/json/v1/1/search.php?s=chicken');
const data = await res.json();
setMeals(data);
}, 5000)
}, [])
return (
<div className="App">
<header>
<h1>Meal Recipes</h1>
</header>
<div className="content">
<Home />
</div>
</div>
);
}
export default App;
Now we need to check if we have our meal results. Let's use conditional rendering in React to display our meal recipe results. Copy and paste the code below into the Home.js
file:
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom'
const Home = ( ) => {
const [meals, setMeals] = useState(null);
useEffect(() => {
setTimeout(async () => {
const res = await fetch(
"https://www.themealdb.com/api/json/v1/1/search.php?s=chicken"
);
const meals = await res.json();
setMeals(meals);
console.log(meals.meals[0])
}, 5000);
}, []);
return (
<>
<div className="bg-gray-900 text-white min-h-screen">
<div className="m-auto max-w-3xl flex flex-col items-center justify-center text-center">
<div id="meals" className="meals">
{meals &&
meals.meals.map((meal) => (
<div className="meal" key={meal.idMeal}>
<Link to={`/MealInfo/${meal.idMeal}`}>
<img
className="meal-img"
src={meal.strMealThumb}
alt={meal.strMeal}
/>
<div className="meal-info" data-mealid={meal.idMeal}>
<h3>{meal.strMeal}</h3>
</div>
</Link>
</div>
))}
</div>
){"}"}
</div>
</div>
</>
);
};
export default Home;
Adding React Router
Since you're using Link
from react-router-dom
, we need to install react-router-dom
in your project. Follow these steps:
Open your terminal or command prompt.
Navigate to your project directory.
Run the following command to install
react-router-dom
using npm:
npm install react-router-dom
Alternatively, if you're using Yarn, you can use this command:
yarn add react-router-dom
- Once the installation is complete, you can import the necessary components from
react-router-dom
and start defining your routes in yourHome.js
file.
Wrapping the App with BrowserRouter
Let’s wrap our App in main.js with BrowserRouter from react-router-dom. This is because we're using Link from react-router-dom in our Home component, which is calling useContext. The context it is looking for is provided by BrowserRouter, but our app is not wrapped by a BrowserRouter. Your main.js should be like this:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import { BrowserRouter } from 'react-router-dom'
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
Conclusion
In conclusion, in Part One of our series, we've laid the groundwork for implementing skeleton loading screens in React applications. By setting up a basic React project, creating components, adding styles, and fetching data from an API, we've prepared the foundation for integrating skeleton loading screens into our application.
In Part Two, we'll dive deeper into the implementation details of skeleton loading screens. We'll explore how to create reusable skeleton components, customize loading animations, and handle various loading scenarios efficiently. Click here for Part Two, where we'll take our skeleton loading screens to the next level!
Subscribe to my newsletter
Read articles from Temitope Ogunleye directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Temitope Ogunleye
Temitope Ogunleye
👋 Hi, I'm Temitope, a Technical Writer with a passion for front-end technologies. I specialize in writing about React, Tailwind CSS, and other web development tools.