Build a Customer Review APP with Strapi and Solid.js
Tags: solidjs, strapi
Published: 2024-07-08 10:52:34

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](https://nodejs.org/en/download) (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](https://www.solidjs.com/guides/getting-started).
- 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:
```bash
npx create-strapi-app@latest my-project
```
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:
```bash
npm run develop
```
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](https://api-prod.strapi.io/uploads/001_welcome_to_strapi_0ad3860440.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](https://api-prod.strapi.io/uploads/002_create_collection_4e80c37e26.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](https://api-prod.strapi.io/uploads/003_add_collection_type_fields_2870a60bb5.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](https://api-prod.strapi.io/uploads/004_add_reviewer_rating_field_67ec4a03c9.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](https://api-prod.strapi.io/uploads/005_the_review_collection_field_37619d7de0.png)

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

![006-collection-type-fields.png](https://api-prod.strapi.io/uploads/006_collection_type_fields_70886d75ff.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](https://api-prod.strapi.io/uploads/007_enable_public_access_ad5dbb20b7.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](https://api-prod.strapi.io/uploads/008_create_an_entry_afff7431b1.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](https://www.solidjs.com/). 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](https://www.solidjs.com/docs/latest) 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:

```bash
npx degit solidjs/templates/js frontend
> cd frontend
> npm i # or yarn or pnpm
> npm run dev 
```
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](https://solid-libs.github.io/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:
```bash
npm install solid-bootstrap
```
Since our focus is not on styling, you can choose to style your project yourself. However, I included minimal [CSS styling](https://github.com/oyedeletemitope/strapi-css-code), 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:
```javascript
  // 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;
```
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:
```javascript
// 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;
```
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:
```javascript
    // 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;
```
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](https://api-prod.strapi.io/uploads/demo_fc0448a1fd.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](https://github.com/oyedeletemitope/Customer-review-app-solid-strapi). 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
* [Github Repo](https://github.com/oyedeletemitope/Customer-review-app-solid-strapi) of the full frontend code.