How to build a Quote sharing app using React.js, React-share and React-paginate
Tags: javascript, react, webdev, beginners
Published: 2022-04-06 10:21:16

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](https://quotweet.vercel.app)

[React-share](https://www.npmjs.com/package/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](https://www.npmjs.com/package/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.

{% embed https://dev.to/arshadayvid/how-to-validate-forms-with-react-hook-form-in-react-apps-2oi3 %}

### 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.

```shell
npx create-react-app@latest quotweet
```

🚀 Run the code below to install react-share

```shell
npm install react-share --save
```

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

```shell
npm install react-paginate --save
```

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

```shell
npm start
```

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

```shell
  npm install -D tailwindcss postcss autoprefixer
```

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

```shell
npx tailwindcss init -p
```

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

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

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

```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

### Project Overview

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

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

export default App;
```

> 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:

```jsx
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;
```

### 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**

```jsx
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;
```

- 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.

```jsx
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;
```

- 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.

```jsx
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;
```

- 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1649193800732/9_JRnPkIv.png)

### 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**

```jsx
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;
```

- 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`

```jsx
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;
```

- 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:

```jsx
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;
```

- 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](https://www.npmjs.com/package/react-paginate) 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

{% embed https://dev.to/arshadayvid %}
### 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](https://twitter.com/Arshadayvid) and [LinkedIn](https://www.linkedin.com/in/david-asaolu234/)

<img src="https://media.giphy.com/media/BYoRqTmcgzHcL9TCy1/giphy.gif"/>