Build a MySQL Node.js CRUD App #3: The Client-Side with React

Subscribe to my newsletter and never miss my upcoming articles

Listen to this article

Hello and welcome to the 3rd part of Let's Build a MySQL Node.js CRUD App series! In the previous article, we have implemented all our GET, POST, PUT and DELETE routes in our server.

If you haven't read the previous article, please find it here.

In this part, we will be working on our front-end with React to complete our MySQL CRUD app!

Step 1: Install npm packages

Install axios and cors to allow our app to make requests to the server. Install react-hook-form for easy form handling in React.

npm install axios cors react-hook-form

Step 2: Import cors

In server.js, include the import statement at the top to use cors.

const cors = require("cors");

Then, to use it, add this line below the const app line.

app.use(cors());

This will allow our client-side at localhost:3001 to be able to make requests to the server at port 3000. To understand more about CORS, feel free to read this article on Introduction to Cross-Origin Resource Sharing.

Step 3: Read Reviews

In App.js, we want to get and display the reviews from MySQL for users to read. To do so, we first import the following:

import { useState, useEffect } from "react";
import axios from "axios";

Next, our code will proceed as:

  1. Initialize an array called reviews, which contains all review data
  2. Create function to GET all the reviews from server store to reviews array.
  3. Create a useEffect hook to update reviews
// 1.
const [reviews, setReviews] = useState([]);
// 2.
const getReviews = () => {
    axios.get("http://localhost:3000/reviews").then((res) => {
      setReviews(res.data);
    });
};
// 3.
useEffect(() => {
    getReviews();
}, [reviews]);

Lastly, to display the data in reviews, we use map() in the return function and envelop it with a <div className="reviews"> as shown:

<div className="reviews">
{reviews.map((item) => {
    return (
       <div className="review">
          <h3>Title: {item.book_title}</h3>
          <h3>Review: {item.book_review}</h3>
          <h3>Rating: {item.book_rating}</h3>
       </div>
    );
})}
</div>

Then run the server with nodemon server.js and start the React app with npm start.

Our app should display data from our database immediately after launching.

read.PNG

Step 3: Create Reviews

Now let's create the components for our React app to perform the Create operation to our database.

Create a file called Add.js, which will be the component that allows the user to create and add a new book review into the database by submitting a form using react-hook-form and axios.

In this file, add the following import statements at the top.

import { useForm } from "react-hook-form";
import axios from "axios";

Then, we will code the following:

  1. Initialize form using useForm
  2. Create an onSubmit handler function that will insert the submitted form data into the database.
  3. Update the reviews array in App.js
export default function Add(props) {
  // 1.
  const { register, handleSubmit } = useForm();
  // 2.
  const onSubmit = (data) => {
    console.log(data);
    addReview(data);
  };

  // function to make a POST req to the server to insert data to MySQL db
  const addReview = (data) => {
    axios.post("http://localhost:3000/reviews", data).then(() => {
      // 4.
      props.setReviews([...props.reviews, {data}]);
    });
  };
}

And then, create the appropriate HTML form in the return function like the example below: image.png

Import this component into App.js and include it like so:

<Add reviews={reviews} setReviews={setReviews} />

Step 4: Update Reviews

We are now able to Create and Read from our MySQL database. Let's work on the Update next.

Create a Edit.js file, a component that returns a form which a user can submit to update values of a specific review data.

To allow the user to update an existing review, we will:

  1. Import the necessary packages to Edit.js
  2. Initialize our React form
  3. Create a function to update our database when the form is submitted
  4. Update our reviews array
// 1.
import { useForm } from "react-hook-form";
import axios from "axios";

export default function Edit(props) {
  // 2.
  const { register, handleSubmit } = useForm();

  // 3.
  const onSubmit = (data) => {
    console.log(data); //{book_review: ..., book_rating: ...}
    data["id"] = props.id; //add the id from props
    updateReview(data);
  };

// This will update existing data with new submitted values
const updateReview = (data) => {
    axios.put("http://localhost:3000/reviews", data).then((res) => {
      // 4.
      props.setReviews(
        props.reviews.map((item) => {
          return item.id === props.id? {
                id: item.id,
                book_title: item.book_title,
                book_review: item.book_review,
                book_rating: item.book_rating,
              }
            : item;
        })
      );
    });
  };

Then, create a simple form in the return function and add the Edit component in App.js.

Add it inside our <div className="reviews"> and remember to pass in id as item.id as attributes from our Edit component to work.

<div className="reviews">
        {reviews.map((item) => {
          return (
            <div className="review">
              <h3>Title: {item.book_title}</h3>
              <h3>Review: {item.book_review}</h3>
              <h3>Rating: {item.book_rating}</h3>
              {/*Add Edit here*/}
              <Edit id={item.id} reviews={reviews} setReviews={setReviews} />
            </div>
          );
        })}
</div>

The result will look like this.

edit.PNG

Step 5: Delete Reviews

Finally, our Delete operation will be the simplest of all. Our client just needs to provide the id of the review to delete from the database.

The request URL will contain the id as params like: http://localhost:3000/:id

So to do this, create a Delete.js file and proceed as follows:

  1. Import axios
  2. Create function to delete a review where :id in the URL is props.id
  3. Update reviews array
  4. Create the delete button in return
// 1.
import axios from "axios";
export default function Delete(props) {
  // 2.
  const deleteReview = () => {
    axios.delete(`http://localhost:3000/reviews/${props.id}`).then((res) => {
      // 3.
      props.setReviews(props.reviews.filter((item) => {
          return item.id !== props.id;
        })
      );
    });
  };

// 4.
return (
    <button className="del-btn" onClick={deleteReview}>
      Delete Review
    </button>
  );
}

As usual, import this component to App.js and insert it below our Edit component. Remember to include the relevant attributes to pass as props for Delete to work.

<div className="review">
    <h3>Title: {item.book_title}</h3>
     <h3>Review: {item.book_review}</h3>
     <h3>Rating: {item.book_rating}</h3>
     <Edit id={item.id} reviews={reviews} setReviews={setReviews} />
      {/*Add Delete here*/}
     <Delete id={item.id} reviews={reviews} setReviews={setReviews} />
</div>

Final result

And now, we should have a simple React app that performs the CRUD operations with our MySQL database!

yay.gif

Thanks for reading! We have finally completed our app but what's next? It's deploying it! In the next part of this series, let's learn how to deploy our full-stack React app to Heroku.

If this series has been helpful so far, do give this article a like or share and stay tuned for the next one. Cheers!


Resources Used

Interested in reading more such articles from Victoria Lo?

Support the author by donating an amount of your choice.

Recent sponsors
Ahmed Bankole's photo

You really dedicated your time into this series.

Nice work by the way 😅😅

Victoria Lo's photo

Thanks Ahmed Bankole! Though I forgot to publish on my usual time (which was yesterday), I still try to publish haha!

Olubisi Idris Ayinde's photo

This is amazing ğŸŽ‰ğŸŽ‰ğŸŽ‰ Thanks for sharing

Victoria Lo's photo

Thanks for reading :)

Pawel's photo

I see one little problem with useState and }, [reviews]). This generates continuos requests to api by getReviews function. Try put console.log inside that function. I saw the same problem in other similar articles. IMO better is let useState has empty [ ], and call getReviews function from each components that make change to database. End effect is the same and there is no unnecessary api requests.