Build a Customer Review APP with Strapi and Solid.js

Strapi - Jul 8 - - Dev Community

Feedback from customers is one key to a successful business today. In this tutorial, we'll learn how to build a customer review and rating App using Strapi, a user-friendly and easy-to-integrate content management system(CMS) that simplifies the content management process, and solid.js, a reactive UI library.

We’ll go through the steps of setting up Strapi CMS backend, creating a content type and also integrating with solid.js to display the data.

Prerequisites

Before we begin, ensure that you have the following:

  • Node installed (it is recommended that you have the latest version of Node installed or one that is compatible with installing Strapi on your local machine).
  • Basic understanding of Javascript.
  • Basic understanding of Solid.js.
  • A code editor.

Setting up Strapi

To kick-start the building process, you need to have Strapi installed on your local machine. If you have already, you can skip this part to where we start building. For the new Strapiers 😀 navigate to the folder you want your project installed in the terminal and run this command:

npx create-strapi-app@latest my-project
Enter fullscreen mode Exit fullscreen mode

Replace my-project with the actual project name you intend on using.

This command will create a new project and install Strapi CMS on your local machine. Once that is installed, you can now start your Strapi application by running the command:

npm run develop
Enter fullscreen mode Exit fullscreen mode

After running that command, copy the URL from your terminal and paste it into any browser of your choice. You’ll need to sign up to access your Strapi dashboard. This shouldn’t take long as the process is seemingly fast and easy. After you’re done signing up, you should have a dashboard like this:

001-welcome-to-strapi.png

Do you love the dark feature? Me too 🙃. You can enable dark mode for yours too. Simply go to your profile settings. Scroll down to Experience and select dark mode in the user interface section.

Now, let’s create some content!

Create a Collection Type

Collection type are a form of content type that is used to manage a list of content which are similar. For this tutorial, you’ll be creating a collection-type content. Navigate to Content-Type Builder on the side navigation bar of your dashboard, click on "Create new collection type".

Create a new collection type by giving it a display name. Ensure it’s singular, not plural, as Strapi automatically pluralizes it. I’ll be naming mine customer-review, go ahead and give yours a unique name.

002-create-collection.png

Click on continue. This will take us to the next step, which is selecting the appropriate fields for your collection type. For the customer-review collection type, you need to create three fields representing:

  • Reviewer's name (reviewers_name): The name of the customer or reviewer that is rating the product. This will be a field of type text, which will be a short text. I’ll name mine reviewers_name.

003-add-collection-type-fields.png

Click on "add another field" to add the next field.

  • Reviewer's rating (reviewers_rating): This will be the actual rating the customer gives to the product. It could be a 5-star rating or a one-star rating. This field will be a number, so go ahead and click the number field, give it the name reviewers_rating, and then choose a number format. I’ll go for integer.

004-add-reviewer-rating-field.png

Click on "add another field" to add the next field.

  • The review (the_review): This will contain the review and feedback of the product by the customer or reviewer. I’ll name mine the_review. This field will also be a text, but a long text.

005-the-review-collection-field.png

Click on "finish" and then save the collection type. Your collection type should look like this:

006-collection-type-fields.png

Enable Public Access

Once that is done, head over to Settings > USERS & PERMISSIONS PLUGIN > Roles, and click on Public. Then scroll down to Permissions, click on customer-review, select all to authorize the application's activity, and click Save.

007-enable-public-access.png

Populate the Collection

You need to add some content to the customer-review collection type that was created. This will help show it works when we integrate it into Solid.js. To do this, head over to Content Manager and navigate to customer-review collection type. Click on "create new entry". Fill in and then save it. You can populate with as much content as you want.

008-create-an-entry.png

So, we’re done with setting up Strapi and our content. Let’s move on to setting up and building the frontend.

Set up the frontend and Integrate with Strapi

For our frontend, we’ll make use of Solid.js. Let’s introduce Solid.js a bit before moving forward.

Created by Ryan Carniato and open-sourced in 2018. Solid.js is a JavaScript framework for building user interfaces. It is a reactive framework that leverages fine-grained reactivity to efficiently update the user interface when data changes.

Solid.js possesses some features, including:

  • Declarative Syntax
  • Fine-Grained Reactivity
  • JSX Support:
  • Reactive Primitives
  • Efficient Rendering

You can check out their documentation to learn more about Solid.js. Let’s proceed to build our frontend.

Install SolidJs

To install Solid.js, navigate once again to the project folder through the terminal. Run the following command:

npx degit solidjs/templates/js frontend
> cd frontend
> npm i # or yarn or pnpm
> npm run dev 
Enter fullscreen mode Exit fullscreen mode

Once you’ve run this command, you should have your solid.js development server start up. Click or copy the URL in the terminal to your browser.

Install Solid-bootstrap

We’ll need to install solid-bootstrap to utilize some of its features like the form, card, button, and others. Run this command:

npm install solid-bootstrap
Enter fullscreen mode Exit fullscreen mode

Since our focus is not on styling, you can choose to style your project yourself. However, I included minimal CSS styling, which I used for this project here. Copy and paste it in the index.css file which you can find in the src folder.

Now we’ve got everything set up to start building.

Create Components

We’ll have two components in addition to the App component: the ReviewForm component and the ReviewCard component. The ReviewForm component will contain the form for which the reviews and ratings are imputed. The ReviewCard component will contain the reviews and ratings given by the customer and saved in the Strapi backend. Of course, the App.jsx component brings them all together.

Inside the src folder, create a subfolder called components. This will house the two components. Create a file inside the subfolder called ReviewCard.jsx and the following code:

  // ReviewCard.jsx
import { createSignal } from "solid-js";
import { Card, Button } from "solid-bootstrap";

function ReviewCard({ review, onDelete }) {
  return (
    <Card className="card">
      <Card.Body>
        <p>{review.attributes.reviewers_name}</p>
        {[...Array(review.attributes.reviewers_rating)].map((_, index) => (
          <span key={index} className="text-warning">
            &#9733;
          </span>
        ))}
        <p>{review.attributes.the_review}</p>
        <Button variant="danger" onClick={() => onDelete(review.id)}>
          Delete
        </Button>
      </Card.Body>
    </Card>
  );
}

export default ReviewCard;
Enter fullscreen mode Exit fullscreen mode

The code above creates a component called ReviewCard,which displays information about the reviews.

Next, still inside the subfolder called components, create another file called ReviewForm. Add the following code:

// ReviewForm.jsx
import { createSignal } from "solid-js";
import { Form, Button } from "solid-bootstrap";

function ReviewForm({ fetchReviews }) {
  const [clicked, setClicked] = createSignal(false);
  const [stars, setStars] = createSignal(0);
  const [hoveredStars, setHoveredStars] = createSignal(0);
  const [name, setName] = createSignal("");
  const [review, setReview] = createSignal("");

  const onMouseOver = (rating) => {
    if (clicked()) return;
    setHoveredStars(rating);
  };

  const onMouseOut = () => {
    if (clicked()) return;
    setHoveredStars(0);
  };

  const onClick = (rating) => {
    setClicked(!clicked());
    setStars(rating);
  };

  const submitReview = async (e) => {
    e.preventDefault();
    const reviewData = {
      data: {
        reviewers_name: name(),
        reviewers_rating: stars(),
        the_review: review(),
      },
    };

    try {
      const response = await fetch(
        "http://localhost:1337/api/customer-reviews/",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify(reviewData),
        },
      );

      if (!response.ok) {
        console.error("Response status:", response.status);
        console.error("Response text:", await response.text());
        throw new Error("Failed to submit review");
      }

      // Re-fetch reviews to include the new submission
      fetchReviews();

      // Reset form
      setName("");
      setReview("");
      setStars(0);
      setHoveredStars(0);
      setClicked(false);
    } catch (error) {
      console.error("Error submitting review:", error);
    }
  };

  return (
    <div className="form-container">
      <Form onSubmit={submitReview}>
        <Form.Group className="star-rating">
          <Form.Label>Your Rating:</Form.Label>
          {[...Array(5)].map((_, i) => (
            <span
              key={i}
              className={`star ${
                i < (hoveredStars() || stars()) ? "selected" : ""
              }`}
              onMouseOver={() => onMouseOver(i + 1)}
              onMouseOut={onMouseOut}
              onClick={() => onClick(i + 1)}
            >
              &#9733;
            </span>
          ))}
        </Form.Group>
        <div className="input-group">
          <Form.Label for="name">Name:</Form.Label>
          <Form.Control
            id="name"
            type="text"
            value={name()}
            onInput={(e) => setName(e.currentTarget.value)}
            className="form-input"
          />
        </div>
        <div className="input-group">
          <Form.Label for="review">Review:</Form.Label>
          <Form.Control
            id="review"
            as="textarea"
            rows={3}
            value={review()}
            onInput={(e) => setReview(e.currentTarget.value)}
            className="form-input"
          />
        </div>
        <Button variant="success" type="submit" disabled={review() === ""}>
          Submit
        </Button>
      </Form>
    </div>
  );
}

export default ReviewForm;
Enter fullscreen mode Exit fullscreen mode

The code above defines the ReviewForm component, which will be used alongside the ReveiwCard component. It includes a form for users to submit reviews, which involves entering a star rating and their names, as well as writing a review. The form handles this submission by sending the data to the Strapi backend through the API endpoint and resets itself after successful submission.

Modify App.jsx

You need to modify App.jsx to most of the logic, which includes fetching the reviews from the Strapi backend, displaying them, and deleting them.

Navigate to your App.jsx file and modify it to this:

    // App.jsx
import { createSignal, createEffect } from "solid-js";
import { Container, Col, Row } from "solid-bootstrap";
import ReviewCard from "./components/ReviewCard";
import ReviewForm from "./components/ReviewForm"; // Import the ReviewForm component
import "./index.css";

function App() {
  const [reviews, setReviews] = createSignal([]);

  // Fetch reviews from Strapi backend
  const fetchReviews = () => {
    fetch("http://localhost:1337/api/customer-reviews/")
      .then((response) => response.json())
      .then((data) => {
        if (Array.isArray(data.data)) {
          setReviews(data.data);
        } else {
          console.error("Expected an array of reviews, but received:", data);
          setReviews([]);
        }
      })
      .catch((error) => console.error("Error fetching reviews:", error));
  };

  createEffect(() => {
    fetchReviews();
  });

  const deleteReview = async (id) => {
    try {
      const response = await fetch(
        `http://localhost:1337/api/customer-reviews/${id}`,
        {
          method: "DELETE",
        },
      );

      if (!response.ok) {
        throw new Error("Failed to delete review");
      }

      // Re-fetch reviews to reflect the deletion
      fetchReviews();
    } catch (error) {
      console.error("Error deleting review:", error);
    }
  };

  return (
    <>
      <Container fluid className="App text-light text-center">
        <Col md={{ span: 6, offset: 3 }}>
          <Row className="mt-5">
            <Col>
              <ReviewForm fetchReviews={fetchReviews} />
            </Col>
          </Row>
          <Row className="mt-5">
            <Col>
              <div className="cards-container">
                {Array.isArray(reviews()) &&
                  reviews().map((r, rIndex) => (
                    <ReviewCard
                      key={rIndex}
                      review={r}
                      onDelete={deleteReview}
                    />
                  ))}
              </div>
            </Col>
          </Row>
        </Col>
      </Container>
    </>
  );
}

export default App;
Enter fullscreen mode Exit fullscreen mode

Here is a breakdown of what the code above does:

  • Fetching reviews: The fetchReview function fetches the reviews from the Strapi backend using the fetch API. It sends a GET request to the Strapi endpoint and updates the reviews state with the fetched data.
  • Review display: The app uses the reviews state to display individual reviews. It checks if the reviews state holds reviews in the form of an array. If it does, it loops through each review object and renders these reviews inside the ReviewCard component, which is responsible for displaying them.
  • Delete reviews: This function utilizes the DeleteReview function to delete reviews. It sends a DELETE request to the Strapi backend with the reviewers ID. If the deletion is successful, it re-fetches the reviews to reflect the deletion.

Demo Time!

All set now, you can check the result in the browser.

demo.gif

Congratulations! You just built a customer review application in Solid.js utilizing Strapi as the backend. Here is the link to the full code on GitHub. Don't forget to give me a star.

Conclusion

In this tutorial, we looked at how to build a customer review application with Solid.Js alongside Strapi as the backend. We went through setting up Strapi for new users; we then proceeded to build our frontend using Solid.js and integrate it with Strapi to display, add, and delete reviews.

Strapi's uses for content purposes are endless, and we'll continue to explore them. Please share if you found this helpful!

Resources

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