DEV Community

Cover image for How to build a Quote sharing app using React.js, React-share and React-paginate
David Asaolu
David Asaolu

Posted on

How to build a Quote sharing app using React.js, React-share and React-paginate

A quote sharing app is a web application that fetches a list of quotes and their authors from an API and allows you to share these quotes on your social media handles with just one click. By building this application, you will learn how to paginate data using React-paginate, and also integrate social media share buttons into your web applications using React-share.

Preview a live demo: Quotweet

React-share is a library that allows you to embed social media share buttons into your web application. It contains numerous social media icons and also supports custom icons.

React-paginate is a ReactJS component used to paginate data. It automatically creates a navigation bar used to move through the data when you pass it some props. It is a very flexible library that lets you style the navigation bar anyhow you want it using vanilla CSS or any CSS framework you prefer.

This article requires a basic understanding of working with APIs in React.js.

Project Setup & Installations

To build this web application, you will have to install create-react-app, react-share and react-paginate.

🚀 Open your terminal

🚀 Install create-react-app, by running the code below.

npx create-react-app@latest quotweet
Enter fullscreen mode Exit fullscreen mode

🚀 Run the code below to install react-share

npm install react-share --save
Enter fullscreen mode Exit fullscreen mode

🚀 Add react-paginate by running the code below in your terminal.

npm install react-paginate --save
Enter fullscreen mode Exit fullscreen mode

🚀 Start the development server. If you are not using Tailwind CSS, you may skip to the next section

npm start
Enter fullscreen mode Exit fullscreen mode

🚀 Optional: Install Tailwind CSS by running the command below. Tailwind CSS is utility-first CSS framework for building mordern user interfaces.

  npm install -D tailwindcss postcss autoprefixer
Enter fullscreen mode Exit fullscreen mode

🚀 Generate tailwind.config.js and postcss.config.js configuration files by running:

npx tailwindcss init -p
Enter fullscreen mode Exit fullscreen mode

🚀 Open tailwind.config.js and copy the code below:

module.exports = {
  content: ['./src/**/*.{js,jsx,ts,tsx}'],
  theme: {
    extend: {},
  },
  plugins: [],
};
Enter fullscreen mode Exit fullscreen mode

🚀 In the ./src/index.css file, add Tailwind directive to your CSS:

@tailwind base;
@tailwind components;
@tailwind utilities;
Enter fullscreen mode Exit fullscreen mode

Project Overview

🚀 Open App.js file and copy the code below:

function App() {
  return (
    <div>
      <Header />
      <Quotes />
      <Pagination />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

From the code snippet above, I divided the web application into three components: headers, quotes, and a pagination bar.

🚀 Create a components folder and create each components in the folder.

Building the Header Component

This contains the title of the web application and other features you may want to add, such as sign-in, and sign-out buttons.

🚀 Open Header.js and create an menu bar for your web application. You can copy the code snippet below:

import React from 'react';

const Header = () => {
  return (
    <header className="w-full h-[10vh] bg-[#1DA1F2] flex flex-col items-center justify-center mb-8 sticky top-0 z-50">
      <h3 className="text-2xl text-[#f7f7f7]">Quotweet</h3>
    </header>
  );
};

export default Header;
Enter fullscreen mode Exit fullscreen mode

How to fetch data from the API and state management

The URL for the quotes is "https://api.quotable.io/quotes" and the author's is "https://images.quotable.dev/profile/200/${authorSlug}.jpg". The author's URL accepts the authorSlug as a parameter to get its image.

In order to make this tutorial simple and easy to follow, all state management will be done in App.js then the values will be passed down to other components as props.

Edit the App.js as follows

import Pagination from './components/Pagination';
import Quotes from './components/Quotes';
import Header from './components/Header';
import { useState, useEffect } from 'react';

function App() {
  const [quotes, setQuotes] = useState([]);  //contains the quotes - array of objects
  const [loading, setLoading] = useState(true);  - //boolean value for checking whether the quotes are available

  const fetchQuotes = () => {
    fetch('https://api.quotable.io/quotes') //URL for fetching all the available quotes
      .then((data) => data.json())
      .then((res) => {
        setQuotes(res.results);
        setLoading(false)
      });
  };

  useEffect(() => {
    fetchQuotes();
  }, []);

  return (
    <div className="w-full min-h-screen">
      <Header />
      {loading ? <p>Loading</p> : <Quotes quotes={quotes} />}
      <Pagination/>
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above,
    • I created two states - quotes for holding the array of quotes and loading for toggling the Quotes component when the data is available or not.
    • In the fetchQuotes function, the API URL returns the quotes in pages (20 per page), then you use setQuotes to save the quotes to the state and change setLoading to false.
    • If the loading state is true - meaning the content is not yet available, it displays loading to the user, then when it's available it displays the quotes via the Quotes component.

Building the Quotes Component

This component contains all the quotes gotten from the API.

import React from 'react';
import QuoteCard from './QuoteCard';

const Quotes = ({ quotes }) => {
  return (
    <main className="w-full flex item-center p-4 flex-wrap gap-6 justify-center max-w-[1500px] min-h-screen">
      {quotes.map((quote) => (
        <QuoteCard quote={quote} key={quote._id} />
      ))}
    </main>
  );
};

export default Quotes;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above
    • The Quotes component accepts the prop quotes - which is an array containing all the quotes received from App.js.
    • I created a QuoteCard component that represents the structure of each quote. Each quotes are then rendered through the QuoteCard component by mapping through the array of quotes.

Building the QuoteCard component

This is the component that describes how the quotes should be displayed.

import React from 'react';

const QuoteCard = ({ quote }) => {
  return (
    <div className="w-[90%] bg-gray-50 sm:w-[300px] rounded-xl  shadow hover:bg-gray-100 flex-col items-center justify-center p-4 text-center">
      <div className="w-full flex items-center justify-center mb-6">
        <img
          src={`https://images.quotable.dev/profile/200/${quote.authorSlug}.jpg`}
          alt={quote.author}
          className="w-[100px] rounded-full"
        />
      </div>

      <div>
        <h3>{quote.author}</h3>
        <p className="opacity-40">{quote.content}</p>
      </div>
    </div>
  );
};

export default QuoteCard;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above,
    • The component receives each quote then displays them according to the layout.
    • The image tag also displays the image using the URL contained in the source attribute. The author's name and the quote are also displayed.

Quotes

How to add Twitter share button using React-share

Since we've been able to fetch the quotes and their images successfully from the API, let's add the Twitter share button to the project.

Edit the QuoteCard component

import React from 'react';
import { TwitterIcon, TwitterShareButton } from 'react-share'; //necessary import

const QuoteCard = ({ quote }) => {
  return (
    <div className="w-[90%] bg-gray-50 sm:w-[300px] rounded-xl  shadow hover:bg-gray-100 flex-col items-center justify-center p-4 text-center">
      <div className="w-full flex items-center justify-center mb-6">
        <img
          src={`https://images.quotable.dev/profile/200/${quote.authorSlug}.jpg`}
          alt={quote.author}
          className="w-[100px] rounded-full"
        />
      </div>

      <div>
        <h3>{quote.author}</h3>
        <p className="opacity-40">{quote.content}</p>
      </div>

      {/* ----- changes made ---- */}
      <div className="icons w-full p-4 flex items-center justify-end">
        <TwitterShareButton
          title={`"${quote.content}" - ${quote.author}`}
          url={'https://twitter.com'}
          via={'Arshadayvid'}
          hashtags={['30DaysOfCode', 'javascript']}
        >
          <TwitterIcon
            size={32}
            round={true}
            className="opacity-40 cursor-pointer hover:opacity-100"
          />
        </TwitterShareButton>
      </div>
      {/* ----- end of react-share ---- */}
    </div>
  );
};

export default QuoteCard;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above,
    • I imported the TwitterIcon - which provides the official icon for Twitter, and the TwitterShareButton - which provides the share via Twitter functionality from react-share.
    • TwitterShareButton wraps the TwitterIcon component and it also receives few props such as - title, url, via and hashtags. Title represents the content you want to share, URL is the twitter homepage link, via is optional and is used for Twitter mentions, and hashtags represents Twitter hashtags you want to add to each share.
    • TwitterIcon also accept props like size and round.

How to add Pagination using React-paginate

React-paginate is a flexible component that is very easy to use.

🚀 Open your Pagination.js file.

🚀 Edit App.js

import Pagination from './components/Pagination';
import Quotes from './components/Quotes';
import Header from './components/Header';
import { useState, useEffect } from 'react';

function App() {
  const [quotes, setQuotes] = useState([]);
  const [totalPages, setTotalPages] = useState(null);
  const [loading, setLoading] = useState(true);

  const fetchQuoteTexts = () => {
    fetch('https://api.quotable.io/quotes')
      .then((data) => data.json())
      .then((res) => {
        setTotalPages(res.totalPages);
        setQuotes(res.results);
        setLoading(false);
      });
  };
  useEffect(() => {
    fetchQuoteTexts();
  }, []);
  return (
    <div className="w-full min-h-screen">
      <Header />
      {loading ? <p>Loading</p> : <Quotes quotes={quotes} />}

      <Pagination
        totalPages={totalPages}
        setQuotes={setQuotes}
        setLoading={setLoading}
      />
    </div>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above:
    • I create a state to hold the total number of pages available, and once the data is available, the state's value changes to the total number of pages retrieved from the API.
    • Pagination accepts totalPages, setQuotes and setLoading as props.

🚀 In the Pagination.js file, we have the following code:

import React from 'react';
import ReactPaginate from 'react-paginate';

function Pagination({ totalPages, setQuotes, setLoading }) {
  const handlePageClick = (data) => {
    const pageNumber = data.selected + 1;
    const fetchData = async () => {
      fetch(`https://api.quotable.io/quotes?page=${pageNumber}`)
        .then((data) => data.json())
        .then((res) => {
          setQuotes(res.results);
          setLoading(false);
        });
    };
    fetchData();
  };

  return (
    <div className="w-full items-center justify-center mx-auto">
      <ReactPaginate
        breakLabel="..."
        nextLabel=">>>"
        previousLabel="<<<"
        onPageChange={handlePageClick}
        pageRangeDisplayed={2}
        marginPagesDisplayed={1}
        pageCount={totalPages}
        renderOnZeroPageCount={null}
        containerClassName="sm:py-4 sm:px-6 p-2 border-2 mt-8 flex items-center justify-center w-2/3 mx-auto mb-10 shadow-lg"
        pageLinkClassName="sm:py-4 sm:px-6 p-2 bg-white"
        previousLinkClassName="sm:py-4 sm:px-6 p-2 bg-white"
        nextLinkClassName="sm:py-4 sm:px-6 p-2 bg-white"
        breakLinkClassName="sm:py-4 sm:px-6 p-2 bg-white"
        activeLinkClassName="bg-blue-100"
      />
    </div>
  );
}

export default Pagination;
Enter fullscreen mode Exit fullscreen mode
  • From the code snippet above,
    • ReactPaginate was imported from the package to display the navigation bar.
    • The breakLabel, previousLabel, and nextLabel represent the value of the break, next and previous buttons.
    • onPageChange contains the function - handlePageClick to be called when a page is changed and also returns the exact value of the button clicked.
    • The function handlePageClick accepts the index of the navigation icon clicked, then adds 1 to the value to fetch the data available on the API.
    • The props ending with ClassName enable you to style the buttons any way you want them.
    • pageCount accepts the total number of pages as a prop from App.js. This value is required.
    • pageRangeDisplayed is the range of pages displayed. Visit the documentation for more information

Conclusion

React-share and React-paginate are two tiny libraries that you can add to your web applications when you need either a social media share feature or pagination.

This is a beginner-friendly project, you can extend it by:

  • Adding authentication - (a sign-in and sign-up feature)
  • Using another API, maybe a joke API where people can share jokes they found funny on their social media handles.
  • Adding other social media sharing features
  • Adding copy and paste feature
  • Using any state management library - Redux, MobX, etc.
  • Improving the design and user interface.

Thank you for reading this far!

Resources

API Documentation - https://github.com/lukePeavey/quotable

Image URL - https://images.quotable.dev/profile/200/${authorSlug}.jpg

Quotes URL - https://api.quotable.io/quotes

Live Demo: https://quotweet.vercel.app

arshadayvid image

Writer's Corner

Hi, I am open to freelance technical writing gigs and remote opportunities. Let's work together. 📧: asaoludavid234@gmail.com

Feel free to connect with me on Twitter and LinkedIn

Discussion (0)