Build a Rating App With React & Strapi

Shada - Oct 27 '21 - - Dev Community

Introduction

In this tutorial, you will learn how to build a rating app with react and Strapi. This is relatively straightforward as we'll be able to give our ratings, reviews and also be able to display them on our webpage. First, let's take a sneak pick into React and Strapi.

What is React?

React is a library for building user interfaces. It runs on the client as a SPA(single page app) but can also build full-stack apps by communicating with a server/API. React is often referred to as a frontend "framework" because it is capable and directly comparable to a framework such as Angular or Vue.

Why should you try it out

  • It structures the "view" layer of your application.
  • Reusable components with their state.
  • JSX-Dynamic markup.
  • Interactive UIs with virtual DOM.
  • Performance and testing.
  • Very popular in the industry.

What is Strapi?

Strapi is an open-source CMS that allows you to build customizable APIs in any frontend application. Strapi is so easy to work with as it will enable you to build flexible APIs with unique features you'll love. You can create custom content types and relationships between the content types so that things can stay organized. It also gives you a media library where you can host your image or audio assets.
Now that we have an understanding of both. Let's get started.

Overview

  • Introduction
  • What is React?
  • What is Strapi?
  • Prerequisites
  • Requirements
  • Installing Strapi
  • Creating the frontend
  • Fetching our API
  • Creating reviews
  • Conclusion

Prerequisites

  • Basic understanding of JavaScript
  • Basic understanding of ReactJS

Requirements

  • Npm
  • React icons which we would install along with our react app
  • Axios

Installing Strapi

We first need to go to our terminal and navigate the project folder you want Strapi to be installed. Strapi can be installed either with Yarn or NPX.

Yarn command:

    yarn create strapi-app rating-app --quickstart
Enter fullscreen mode Exit fullscreen mode

npx command:

    npx create-strapi-app rating-app --quickstart
Enter fullscreen mode Exit fullscreen mode

This will install all the needed packages for this project. After the installation, our Strapi app will automatically be launched in our browser. We should have something like this:

To finish up, create admin by signing up. Now that we've created our Strapi, let's head over to the content-type builder link.

You'll see three different headings, namely

  • Collection types
  • Single types
  • Component

Collection types are what we're interested in. These are types that we'll have more than one of on our website. A single type would be for a unique piece of content, for example, a homepage that might have a title and about section. A component is just a collection of fields that you might use in many different types.

Click on collection types, give it a display name, and make sure it's singular, not plural. Strapi automatically pluralizes it. We will call our collection review.

Next, we have to define a field and field type. For our reviews, we want a title field that is of type text. Then we give the field a name called Name. It can be short text or long text. The short text is best for the title.

Click on the advanced setting and make it a required field or a unique field so that no two review titles can be the same.

Click on add another field. Now we need a rating field that will be a number, so go ahead and click the number and give this the name of Rating and then choose a number format. Ours will be an integer.

We then go to advance setting and make it required and add a minimum value of 1 and a max value that will be 5, so this field can only be a number from one to 5.

Let's add another field for the body, and that's going to be rich text to add formats into it. Click on it and give it a name called reviews we could go to the advanced and make it required, and now we could click on finish.

So now we have all the different fields for our content-type reviews. Click save to save our reviews. Next, we head over to settings. Navigate to Roles and click on the public. We then scroll down to permissions and click on select all.

Strapi saves and refreshes the browser. Now that it's done, we can see the content type review, and if we click on that, we can see our empty review list.

Let's click on add new reviews and then add reviews(I'll add up to 4 reviews in the list), then click on publish after filling out the fields.

Capture d’écran 2021-10-27 à 15.06.07.png

So if we try to retrieve it by using the Strapi API, It sends us the data.

To start your Strapi app always run:

    npm run develop
Enter fullscreen mode Exit fullscreen mode

If you got it up to this part, you're a genius!!! Now let's move to the next step, which is creating the front end.

Creating the front end

As earlier stated, React would be used as our frontend. So let's go ahead and install React and all the packages we'll need. Let's open up our terminal once more and navigate the folder where we want to install react.

Run the command:

    npx create react-app react-ratings
Enter fullscreen mode Exit fullscreen mode

Next, navigate to the folder where we just installed React.

    cd react-ratings
Enter fullscreen mode Exit fullscreen mode

Next, we want to install React icons.

    npm install react-icons
Enter fullscreen mode Exit fullscreen mode

And lastly, let’s install Axios.

    npm install axios
Enter fullscreen mode Exit fullscreen mode

Now that's done, let's remove and edit the things we don't want in our react app. First of all, let's delete all the files highlighted in the image below as we won't be needing them.

Also, we'll head to our app.js and remove the import from '.logo.svg'; the import './app.css'.

Next, we will also delete the headers in our App() function.

Next, head over to head index.js and delete the import reportWebvitals from ./reportWebvitals; the import .index.css; and reportWebvitals() . The final look of our index.js should be like this:

Now that we are done let’s start our react app:

    npm start
Enter fullscreen mode Exit fullscreen mode

We'll see a blank page. This means we are on the right track.

Reading and fetching our reviews.

First we create a folder in our src folder, name it api, next we create a file inside called index.js and add the following codes to it:

    import axios from 'axios';
    const url = "http://localhost:1337/reviews";
    export const readReviews = ()=>axios.get(url);
    export const createReview = newReview =>axios.post(url,newReview);
Enter fullscreen mode Exit fullscreen mode

This includes explanations:

  • Importing Axios to our app.
  • The path to our API.
  • Creating reviews(adding data to our or review API via webpage).
  • Reading the reviews data.

Axios will be responsible for fetching data from our API and also adding to it. We then go back to our app.js and import the just created API folder containing the index.js to our app:

    import * as api from "./api";
Enter fullscreen mode Exit fullscreen mode

We also be using bootstrap so let’s copy the bootstrap link to our index.html in our public folder.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
Enter fullscreen mode Exit fullscreen mode

Now we have everything to start building.

Fetching our API

To do this, we are going to be using the useState and the useEffect. The useState will allow us to state variables, while the useEffect will fulfill our parameters in our components when it renders.

Let’s head over app.js and write the following at the top:

    import React, { useState, useEffect } from "react";
    import { FaStar } from "react-icons/fa";
    const colors = {
      orange: "#FFBA5A",
      grey: "#a9a9a9",
    };
Enter fullscreen mode Exit fullscreen mode

Under our function app() in our app.js we write this:

    const [review, setReview] = useState({});
      const [reviews, setReviews] = useState([]);
      useEffect(
        () => {
          const fetchData = async () => {
            const result = await api.readReviews();

            setReviews(result.data);
          };
          fetchData();
              },[])
                  )
Enter fullscreen mode Exit fullscreen mode

Next, lets create the contents where our API will be displayed

     <section id="reviews">
            <div class="reviews-heading">
              <span>REVIEWS FROM CUSTOMERS</span>
            </div>
            <div className="container">
              <div className="row">
                  <div className="col-md-6">
                    <div class="reviews-box">
                      <div class="box-top">
                        <div class="profile">
                          <div class="name-user">
                            <strong>Temitope</strong>
                          </div>
                        </div>
                            <FaStar key={i} size={18} color={colors.orange} />
                        </div>
                      </div>
                   <div class="client-comment">lorem ipsum lorem ipsumlorem ipsumvvvlorem ipsumlorem ipsumlorem ipsumlorem ipsum </div>
                    </div>
                  </div>
              </div>
            </div>
          </section>
Enter fullscreen mode Exit fullscreen mode

If you encounter an error saying jsx must have one parent element. Do not worry. We can fix this by using <React.Fragment/> or simply use the short syntax <></>.

Let's give it a styling. Create a folder under the src folder and name it styles, then create a file and name it anything you like. Add the following codes to it:

     *{
      margin: 0px;
      padding: 0px;
      font-family: poppins;
      box-sizing: border-box;
    }
    a{
      text-decoration: none;
    }
    #reviews{
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
      width:100%;
    }
    .reviews-heading{
      letter-spacing: 1px;
      margin: 30px 0px;
      padding: 10px 20px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }

    .reviews-heading span{
      font-size: 1.3rem;
      color: #252525;
      margin-bottom: 10px;
      letter-spacing: 2px;
      text-transform: uppercase;
    }
    .reviews-box-container{
      display: flex;
      justify-content: center;
      align-items: center;
      flex-wrap: wrap;
      width:100%;
    }
    .reviews-box{
      width:500px;
      box-shadow: 4px 4px 40px rgba(0,0,0,0.1);
      background-color: #ffffff;
      padding: 20px;
      margin: 15px;
      cursor: pointer;
    }

    .name-user{
      display: flex;
      flex-direction: column;
    }
    .name-user strong{
      color: #3d3d3d;
      font-size: 1.1rem;
      letter-spacing: 0.5px;
    }
    .name-user span{
      color: #979797;
      font-size: 0.8rem;
    }

    .box-top{
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 20px;
    }
    .client-comment p{
      font-size: 0.9rem;
      color: #4b4b4b;
    }
    .reviews-box:hover{
      transform: translateY(-10px);
      transition: all ease 0.3s;
    }

    @media(max-width:1060px){
      .reviews-box{
          width:45%;
          padding: 10px;
      }
    }
    @media(max-width:790px){
      .reviews-box{
          width:100%;
      }
      .reviews-heading h1{
          font-size: 1.4rem;
      }
    }
    @media(max-width:340px){
      .box-top{
          flex-wrap: wrap;
          margin-bottom: 10px;
      }
      .reviews{
          margin-top: 10px;
      }
    }
    ::selection{
      color: #ffffff;
      background-color: #252525;
    }
Enter fullscreen mode Exit fullscreen mode

Head back to app.js and import the CSS:

    import "./styles/review_style.css";
Enter fullscreen mode Exit fullscreen mode

Now that we've done that let's start adding API values to our contents. Under the the div class row add this code:

    {reviews.map((review, i) => (
Enter fullscreen mode Exit fullscreen mode

Close it before the last two divs

      ))}
Enter fullscreen mode Exit fullscreen mode

The .map function helps us to iterate our list of data.

For our name, we go to where we wrote name (between our strong tags) and replace it with this:

    {review.Name}
Enter fullscreen mode Exit fullscreen mode

To display our rating, replace it with this code:

     {Array.from({ length: review.Rating }).map((i) => (
                            <FaStar key={i} size={18} color={colors.orange} />
                          ))}
Enter fullscreen mode Exit fullscreen mode

Lastly, for our reviews, we change the dummy texts with:

    {review.review}
Enter fullscreen mode Exit fullscreen mode

If you’re confused at this stage, copy and paste this the code below and you should get right back on track.

     <section id="reviews">
            <div class="reviews-heading">
              <span>REVIEWS FROM CUSTOMERS</span>
            </div>

            <div className="container">
              <div className="row">
                {reviews.map((review, i) => ( // calling the api
                  <div className="col-md-6">
                    <div class="reviews-box">
                      <div class="box-top">
                        <div class="profile">
                          <div key={i}></div>
                          <div class="name-user">
                            <strong>{review.Name}</strong>
                          </div>
                        </div>

                        <div style={styles.stars}>
                          {Array.from({ length: review.Rating }).map((i) => (
                            <FaStar key={i} size={18} color={colors.orange} />
                          ))}
                        </div>
                      </div>
                      <div class="client-comment">{review.review}</div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </section>
Enter fullscreen mode Exit fullscreen mode

Now that we're done, we should be able to view our review API.

Creating reviews

Now let’s create a form for rating and reviewing. Write the following code in our function app() above our section displaying our reviews.

    <div style={styles.container}>
      <h2>star ratings app in react</h2>
          <div style ={styles.stars}>
          {stars.map((_, index) => {
            return(
              <FaStar
                key={index}
                size ={24}
                style={{
                  marginRight :10,
                  cursor:"pointer"
                }} 
                color ={(hoverValue || currentValue)> index ? colors.orange : colors.grey}
                onClick={() => handleClick(index + 1)}
                onMouseOver={()=> handleMouseOver(index + 1)}

              />
            )
          })}
        </div>
    <div>
                <input
                  type="text"
                  placeholder="input your name"
                  required
                  style={styles.input />
              </div>

        <textarea
         placeholder="what's your feedback"
         style={styles.textarea}
        />
        <button style={styles.button}>submit</button>
          </div>
Enter fullscreen mode Exit fullscreen mode

Input this code above the form just after our function app():

    const stars = Array(5).fill(0);
      const [currentValue, setCurrentValue] = React.useState(0);
      const [hoverValue, setHoverValue] = React.useState(undefined);
      const handleClick = (value) => {
        setCurrentValue(value);
      };
      const handleMouseOver = (value) => {
        setHoverValue(value);
      };
Enter fullscreen mode Exit fullscreen mode

We then add an inline css to it. This should be written below just before the export default app.

    const styles = {
    container: {
        align: "center",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        boxShadow: "0 0 20px 0 #999",
        width: "30%",
        margin: "50px auto",
        flexDirection: "column",
      },
      input: {
        borderRaduis: 5,
        width: 300,
        margin: "10px 0",
        marginDown: "15px",
        minHeight: 30,
        padding: 1,
        height: "20px",
      },
      textarea: {
        border: "1px solid #a9a9a9",
        borderRaduis: 5,
        width: 300,
        margin: "20px 0",
        minHeight: 100,
        padding: 10,
      },

      button: {
        border: "1px solid #a9a9a9",
        borderRaduis: 5,
        width: 300,
        padding: 10,
        margin: "20px 0",
      },
    };
Enter fullscreen mode Exit fullscreen mode

Inside star icons(fastar), replace the onClick function we wrote with:

     onClick={() => {
                        setReview({ ...review, Rating: index + 1 });
                      }}
Enter fullscreen mode Exit fullscreen mode

Next add the following inside our input tag:

    value={review.Name}
                  onChange={(e) => setReview({ ...review, Name: e.target.value })}
Enter fullscreen mode Exit fullscreen mode

We will also add to text area and button. For text area:

    value={review.review}
                onChange={(e) => setReview({ ...review, review: e.target.value })}
Enter fullscreen mode Exit fullscreen mode

and lastly, for button:

     onClick={createReview}
Enter fullscreen mode Exit fullscreen mode

Confused? Here’s the full code for the form.

     <form>
    <div style={styles.container}>
              <h2>RATE OUR SERVICE</h2>

              <div style={styles.stars}>
                {stars.map((_, index) => {
                  return (
                    <FaStar
                      key={index}
                      size={24}
                      style={{
                        marginRight: 10,
                        cursor: "pointer",
                      }}
                      color={
                        (hoverValue || currentValue) > index
                          ? colors.orange
                          : colors.grey
                      }
                      onClick={() => {
                        setReview({ ...review, Rating: index + 1 });
                      }}
                      onMouseOver={() => handleMouseOver(index + 1)}
                    />
                  );
                })}
              </div>
              <div>
                <input
                  type="text"
                  placeholder="input your name"
                  required
                  style={styles.input}
                  value={review.Name}
                  onChange={(e) => setReview({ ...review, Name: e.target.value })}
                />
              </div>

              <textarea
                placeholder="what's your feedback"
                required
                style={styles.textarea}
                value={review.review}
                onChange={(e) => setReview({ ...review, review: e.target.value })}
              />
              <button
                type="submit"
                style={styles.button}
                class="btn btn-primary"
                onClick={createReview}
              >
                submit
              </button>
            </div>
          </form>
Enter fullscreen mode Exit fullscreen mode

We’re almost done. Let’s scroll up and type the following below our use effect code:

      const createReview = async () => {
      try {
          console.log(review);
          const data = await api.createReview(review);
          setReview([...reviews, data]);
        } catch (error) {
          console.log(error);
        }
      };
      let [reviewCount, setreviewCount] = useState([]);
      const setCountFxn = (no) => {
        setReview(no);
      };

          const data = await api.createReview(review);
          setReview([...reviews, data]);
        } catch (error) {
          console.log(error);
        }
      };
      let [reviewCount, setreviewCount] = useState([]);
      const setCountFxn = (no) => {
        setReview(no);
      };
Enter fullscreen mode Exit fullscreen mode

Now we're done. Suppose you get an error that's because you didn't add the tag <></> like I earlier suggested(don't add if you're not getting an error).

Here's the complete code of app.js. You have to edit them to do your values.

    import React, { useState, useEffect } from "react";
    import * as api from "./api";
    import "./styles/review_style.css";
    import { FaStar } from "react-icons/fa";
    const colors = {
      orange: "#FFBA5A",
      grey: "#a9a9a9",
    };

    function App() {
      const stars = Array(5).fill(0);
      const [currentValue, setCurrentValue] = React.useState(0);
      const [hoverValue, setHoverValue] = React.useState(undefined);

      const handleClick = (value) => {
        setCurrentValue(value);
      };

      const handleMouseOver = (value) => {
        setHoverValue(value);
      };

      const handleMouseLeave = () => {
        setHoverValue(undefined);
      };
      const [review, setReview] = useState({});
      const [reviews, setReviews] = useState([]);
      useEffect(
        () => {
          const fetchData = async () => {
            const result = await api.readReviews();
            // console.log(result);
            setReviews(result.data);
          };
          fetchData();


        },

        []
      );
      const createReview = async () => {
        try {
          console.log(review);
          const data = await api.createReview(review);
          setReview([...reviews, data]);
        } catch (error) {
          console.log(error);
        }
      };
      let [reviewCount, setreviewCount] = useState([]);
      const setCountFxn = (no) => {
        setReview(no);
      };
      return (
        <>
          <form>
            <div style={styles.container}>
              <h2>RATE OUR SERVICE</h2>

              <div style={styles.stars}>
                {stars.map((_, index) => {
                  return (
                    <FaStar
                      key={index}
                      size={24}
                      style={{
                        marginRight: 10,
                        cursor: "pointer",
                      }}
                      color={
                        (hoverValue || currentValue) > index
                          ? colors.orange
                          : colors.grey
                      }
                      onClick={() => {
                        setReview({ ...review, Rating: index + 1 });
                      }}
                      onMouseOver={() => handleMouseOver(index + 1)}
                    />
                  );
                })}
              </div>
              <div>
                <input
                  type="text"
                  placeholder="input your name"
                  required
                  style={styles.input}
                  value={review.Name}
                  onChange={(e) => setReview({ ...review, Name: e.target.value })}
                />
              </div>

              <textarea
                placeholder="what's your feedback"
                required
                style={styles.textarea}
                value={review.review}
                onChange={(e) => setReview({ ...review, review: e.target.value })}
              />
              <button
                type="submit"
                style={styles.button}
                class="btn btn-primary"
                onClick={createReview}
              >
                submit
              </button>
            </div>
          </form>

          <section id="reviews">
            <div class="reviews-heading">
              <span>REVIEWS FROM CUSTOMERS</span>
            </div>

            <div className="container">
              <div className="row">
                {reviews.map((review, i) => ( // calling the api
                  <div className="col-md-6">
                    <div class="reviews-box">
                      <div class="box-top">
                        <div class="profile">
                          <div key={i}></div>
                          <div class="name-user">
                            <strong>{review.Name}</strong>
                          </div>
                        </div>

                        <div style={styles.stars}>
                          {Array.from({ length: review.Rating }).map((i) => (
                            <FaStar key={i} size={18} color={colors.orange} />
                          ))}
                        </div>
                      </div>

                      <div class="client-comment">{review.review}</div>
                    </div>
                  </div>
                ))}
              </div>
            </div>
          </section>
        </>
      );
    }

    const styles = {
      container: {
        align: "center",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
        boxShadow: "0 0 20px 0 #999",
        width: "30%",
        margin: "50px auto",
        flexDirection: "column",
      },
      input: {
        borderRaduis: 5,
        width: 300,
        margin: "10px 0",
        marginDown: "15px",
        minHeight: 30,
        padding: 1,
        height: "20px",
      },
      textarea: {
        border: "1px solid #a9a9a9",
        borderRaduis: 5,
        width: 300,
        margin: "20px 0",
        minHeight: 100,
        padding: 10,
      },

      button: {
        border: "1px solid #a9a9a9",
        borderRaduis: 5,
        width: 300,
        padding: 10,
        margin: "20px 0",
      },
    };
    export default App;
Enter fullscreen mode Exit fullscreen mode

Here’s also a link to the full source code on github.

Conclusion

We learned about contents types and how to build a rating app using Strapi as our backend. We created content, fed them to our website through Strapi's API. We also learned how to create reviews and feed them (insert) into our database.

The truth is there are countless things we can use Strapi for, and this is just one of them.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .