Skeleton Loaders: Simplifying Data Loading in React: Part 1

topeogunleye - Jun 19 - - Dev Community

Image description

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
Enter fullscreen mode Exit fullscreen mode

Next, navigate into the newly created project directory:

cd react-skeleton-screens
Enter fullscreen mode Exit fullscreen mode

Open the project in Visual Studio Code:

code .
Enter fullscreen mode Exit fullscreen mode

Removing Boilerplate Code

Let's clean up the default files created by create-react-app:

  1. Open the src folder and delete the following files:

    • App.css
    • App.test.js
    • logo.svg
    • setupTests.js
  2. In index.js, remove the import and invocation of the service worker.

  3. In App.js, remove the import of logo.svg and App.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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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 */
}
Enter fullscreen mode Exit fullscreen mode

Explanation of Styles

  1. The universal selector (*) is used to apply the following styles to all elements on the page:

    • margin: 0; and padding: 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.
  2. 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.
  3. 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.
  4. 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.
  5. Media queries:

    • Adjust the number of columns in the .meals grid based on screen width.
  6. The .meal class:

    • Creates a card-like styling for meal items.
    • Sets border radius, overflow behavior, box shadow, cursor, and dimensions.
  7. 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
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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:

  1. Open your terminal or command prompt.
  2. Navigate to your project directory.
  3. Run the following command to install react-router-dom using npm:
npm install react-router-dom
Enter fullscreen mode Exit fullscreen mode

Alternatively, if you're using Yarn, you can use this command:

yarn add react-router-dom
Enter fullscreen mode Exit fullscreen mode
  1. Once the installation is complete, you can import the necessary components from react-router-dom and start defining your routes in your Home.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>
);
Enter fullscreen mode Exit fullscreen mode

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!

. . .