Adding Loading Spinner And Variable Page Size To Out News App

Posted on July 15, 2026 by Vishesh Namdev
Python C C++ Javascript React JS
Adding Loading Spinner & Variable Page Size to Our News App

Adding Loading Spinner & Variable Page Size to Our News App ⏳🔢 Right now, when we click Previous or Next, the page number changes instantly — but in a real app, fetching new articles from an API takes time. Users need visual feedback that something is happening. On top of that, a fixed pageSize of 6 articles isn't very flexible — some users may want to see more articles per page, others fewer.

In this tutorial, we will learn:

  • Why a loading spinner improves user experience
  • Adding a loading flag to component state
  • Creating a reusable Spinner component
  • Simulating network delay with setTimeout
  • Letting users choose a custom pageSize with a dropdown
  • Resetting to page 1 whenever the page size changes
  • ---

    Why Do We Need a Loading Spinner?

    Whenever our app fetches new articles — whether by clicking Next, Previous, or changing the page size — there's a short delay before the data is ready. Without any feedback, the screen looks frozen and users might click the button multiple times. A loading spinner tells the user "hang on, we're getting your articles" and also lets us disable the buttons while a request is in progress.

    ---

    Step 1: Create a Spinner Component

    Let's create a small, reusable Spinner.js component using Bootstrap's spinner classes.

    import React from "react";
    import loadingGif from "./loading.gif";
    
    const Spinner = () => {
      return (
        <div className="text-center my-4">
          <img src={loadingGif} alt="loading" style={{ width: "80px" }} />
          <h5>Loading articles...</h5>
        </div>
      );
    };
    
    export default Spinner;
    ---

    Step 2: Add loading and pageSize to State

    Open News.js and update the state to include a loading flag. We'll keep pageSize as a prop-driven default, but make it changeable via state so the user can pick their own value.

    import React, { Component } from "react";
    import NewsItem from "./NewsItem";
    import Spinner from "./Spinner";
    
    export class News extends Component {
    
      static defaultProps = {
        pageSize: 6,
        totalResults: 30,
      };
    
      state = {
        articles: [],
        page: 1,
        loading: false,
        pageSize: this.props.pageSize,
      };
    
      render() {
        return (
          <div className="container my-4">
            <h2 className="text-center mb-4">Welcome to News App 📰</h2>
          </div>
        );
      }
    }
    
    export default News;
    ---

    Step 3: Simulate a Delay with setTimeout

    Real API calls take time, so let's write a helper method fetchArticles that turns loading on, waits a moment using setTimeout (standing in for our future API call), and then turns loading back off.

    fetchArticles = () => {
      this.setState({ loading: true });
    
      // Simulated network delay — will be replaced by a real API call later
      setTimeout(() => {
        this.setState({ loading: false });
      }, 1000);
    };
    ---

    Step 4: Update handlePrevClick and handleNextClick

    Now let's call fetchArticles every time the page changes, so the spinner shows up during the "fetch".

    handlePrevClick = () => {
      this.setState({ page: this.state.page - 1 }, () => {
        this.fetchArticles();
      });
    };
    
    handleNextClick = () => {
      this.setState({ page: this.state.page + 1 }, () => {
        this.fetchArticles();
      });
    };
    ---

    Step 5: Add a Page Size Selector

    Let's give users a dropdown to choose how many articles they want per page. Whenever the page size changes, we reset page back to 1 so the pagination stays consistent.

    handlePageSizeChange = (e) => {
      const newSize = Number(e.target.value);
      this.setState({ pageSize: newSize, page: 1 }, () => {
        this.fetchArticles();
      });
    };

    And here's the dropdown JSX we'll place near the top of our articles list:

    <div className="d-flex justify-content-end mb-3">
      <label className="me-2 align-self-center fw-bold">Articles per page:</label>
      <select
        className="form-select w-auto"
        value={this.state.pageSize}
        onChange={this.handlePageSizeChange}
      >
        <option value="3">3</option>
        <option value="6">6</option>
        <option value="9">9</option>
        <option value="12">12</option>
      </select>
    </div>
    ---

    Step 6: Render the Spinner Conditionally

    Inside render(), we check this.state.loading. If it's true, we show the Spinner instead of the articles. We also disable the Previous/Next buttons while loading.

    render() {
      const { totalResults } = this.props;
      const { page, pageSize, articles, loading } = this.state;
    
      return (
        <div className="container my-4">
          <h2 className="text-center mb-4">Welcome to News App 📰</h2>
    
          <div className="d-flex justify-content-end mb-3">
            <label className="me-2 align-self-center fw-bold">Articles per page:</label>
            <select
              className="form-select w-auto"
              value={pageSize}
              onChange={this.handlePageSizeChange}
            >
              <option value="3">3</option>
              <option value="6">6</option>
              <option value="9">9</option>
              <option value="12">12</option>
            </select>
          </div>
    
          {loading ? (
            <Spinner />
          ) : (
            articles.map((article, index) => (
              <NewsItem
                key={index}
                title={article.title}
                description={article.description}
                imageUrl={article.urlToImage}
                newsUrl={article.url}
              />
            ))
          )}
    
          {/* Previous & Next Buttons */}
          <div className="d-flex justify-content-between my-4">
            <button
              className="btn btn-warning"
              onClick={this.handlePrevClick}
              disabled={loading || page <= 1}
            >
              &larr; Previous
            </button>
    
            <span className="align-self-center fw-bold">
              Page {page} of {Math.ceil(totalResults / pageSize)}
            </span>
    
            <button
              className="btn btn-warning"
              onClick={this.handleNextClick}
              disabled={loading || page >= Math.ceil(totalResults / pageSize)}
            >
              Next &rarr;
            </button>
          </div>
    
        </div>
      );
    }
    ---

    Step 7: Full Updated News.js

    Here is the complete News.js file with the loading spinner and variable page size wired together.

    import React, { Component } from "react";
    import NewsItem from "./NewsItem";
    import Spinner from "./Spinner";
    
    export class News extends Component {
    
      static defaultProps = {
        pageSize: 6,
        totalResults: 30,
      };
    
      state = {
        articles: [
          {
            title: "React 19 Released",
            description: "React 19 brings exciting improvements for developers.",
            urlToImage: "",
            url: "#",
          },
          {
            title: "GitHub Pages Hosting Made Easy",
            description: "Deploy your React app for free using GitHub Pages.",
            urlToImage: "",
            url: "#",
          },
          {
            title: "JavaScript ES2025 Features",
            description: "Explore the newest JavaScript features landing in 2025.",
            urlToImage: "",
            url: "#",
          },
        ],
        page: 1,
        loading: false,
        pageSize: this.props.pageSize,
      };
    
      fetchArticles = () => {
        this.setState({ loading: true });
    
        setTimeout(() => {
          this.setState({ loading: false });
        }, 1000);
      };
    
      handlePrevClick = () => {
        this.setState({ page: this.state.page - 1 }, () => {
          this.fetchArticles();
        });
      };
    
      handleNextClick = () => {
        this.setState({ page: this.state.page + 1 }, () => {
          this.fetchArticles();
        });
      };
    
      handlePageSizeChange = (e) => {
        const newSize = Number(e.target.value);
        this.setState({ pageSize: newSize, page: 1 }, () => {
          this.fetchArticles();
        });
      };
    
      render() {
        const { totalResults } = this.props;
        const { page, pageSize, articles, loading } = this.state;
    
        return (
          <div className="container my-4">
            <h2 className="text-center mb-4">Welcome to News App 📰</h2>
    
            <div className="d-flex justify-content-end mb-3">
              <label className="me-2 align-self-center fw-bold">Articles per page:</label>
              <select
                className="form-select w-auto"
                value={pageSize}
                onChange={this.handlePageSizeChange}
              >
                <option value="3">3</option>
                <option value="6">6</option>
                <option value="9">9</option>
                <option value="12">12</option>
              </select>
            </div>
    
            {loading ? (
              <Spinner />
            ) : (
              articles.map((article, index) => (
                <NewsItem
                  key={index}
                  title={article.title}
                  description={article.description}
                  imageUrl={article.urlToImage}
                  newsUrl={article.url}
                />
              ))
            )}
    
            <div className="d-flex justify-content-between my-4">
              <button
                className="btn btn-warning"
                onClick={this.handlePrevClick}
                disabled={loading || page <= 1}
              >
                &larr; Previous
              </button>
    
              <span className="align-self-center fw-bold">
                Page {page} of {Math.ceil(totalResults / pageSize)}
              </span>
    
              <button
                className="btn btn-warning"
                onClick={this.handleNextClick}
                disabled={loading || page >= Math.ceil(totalResults / pageSize)}
              >
                Next &rarr;
              </button>
            </div>
    
          </div>
        );
      }
    }
    
    export default News;
    ---

    How the New State Values Work

    State Value Purpose Changes When
    loading Controls whether the Spinner or the article list is shown Before/after every simulated fetch
    pageSize Determines how many articles are shown per page and how total pages are calculated When the user picks a new value from the dropdown
    page Tracks which page of results is currently visible On Previous/Next clicks, or reset to 1 when pageSize changes
    ---

    Features and Learnings:-

  • Understood why loading feedback matters for a smooth user experience.
  • Created a reusable Spinner component.
  • Added a loading flag to state and toggled it around a simulated fetch.
  • Disabled Previous/Next buttons while a "fetch" is in progress.
  • Made pageSize dynamic using a dropdown selector.
  • Reset page to 1 whenever the page size changes, keeping pagination accurate.
  • Prepared the app for the next step: replacing the simulated delay with a real News API call.
  • 📢 Important Note 📢

    How did you feel about this post?

    😍 🙂 😐 😕 😡

    Was this helpful?

    👍 👎