React JS PHP MySQLi CRUD application with React Context API

In this tutorial, you will learn how you can create a simple CRUD application using React JS and PHP MySQLi.

First, we will create CRUD RESTful API using PHP and MySQLi, and then we will implement this API in React js CRUD application.


A Very Simple PHP CRUD RESTful API

1. Database Setup for PHP CRUD REST API

  • Database name – react_php_crud
  • Table name – users

Use the following SQL code to create the users table and the structure of the users table.

CREATE TABLE users (
   id int(11) NOT NULL AUTO_INCREMENT,
   user_name varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
   user_email varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
    PRIMARY KEY (id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2. Creating PHP CRUD application

Open your Xampp htdocs folder or Wamp www directory, and create a new folder called php-react.

php-react folder

After that, we have to create the follwing PHP files inside the php-react folder.

  • db_connection.php
  • add-user.php
  • all-users.php
  • update-user.php
  • delete-user.php

db_connection.php is for making the database connection. Configure this file according to your database configuration.

<?php
$db_conn = mysqli_connect("localhost","root","","react_php_crud");

add-user.php is for inserting new users into the database.

  • POST – /add-user.php
  • Required Fields – user_name, user_email
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: POST");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

require 'db_connection.php';

// POST DATA
$data = json_decode(file_get_contents("php://input"));

if (
    isset($data->user_name)
    && isset($data->user_email)
    && !empty(trim($data->user_name))
    && !empty(trim($data->user_email))
) {
    $username = mysqli_real_escape_string($db_conn, trim($data->user_name));
    $useremail = mysqli_real_escape_string($db_conn, trim($data->user_email));
    if (filter_var($useremail, FILTER_VALIDATE_EMAIL)) {
        $insertUser = mysqli_query($db_conn, "INSERT INTO `users`(`user_name`,`user_email`) VALUES('$username','$useremail')");
        if ($insertUser) {
            $last_id = mysqli_insert_id($db_conn);
            echo json_encode(["success" => 1, "msg" => "User Inserted.", "id" => $last_id]);
        } else {
            echo json_encode(["success" => 0, "msg" => "User Not Inserted!"]);
        }
    } else {
        echo json_encode(["success" => 0, "msg" => "Invalid Email Address!"]);
    }
} else {
    echo json_encode(["success" => 0, "msg" => "Please fill all the required fields!"]);
}

all-users.php is to fetch all users that exist in the database.

  • GET – /all-users.php
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: GET");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

require 'db_connection.php';

$allUsers = mysqli_query($db_conn, "SELECT * FROM `users`");
if (mysqli_num_rows($allUsers) > 0) {
    $all_users = mysqli_fetch_all($allUsers, MYSQLI_ASSOC);
    echo json_encode(["success" => 1, "users" => $all_users]);
} else {
    echo json_encode(["success" => 0]);
}

update-user.php is for updating an existing user.

  • POST – /update-user.php
  • Required Fileds – id, user_name, user_email
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: POST");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

require 'db_connection.php';

$data = json_decode(file_get_contents("php://input"));

if (
    isset($data->id)
    && isset($data->user_name)
    && isset($data->user_email)
    && is_numeric($data->id)
    && !empty(trim($data->user_name))
    && !empty(trim($data->user_email))
) {
    $username = mysqli_real_escape_string($db_conn, trim($data->user_name));
    $useremail = mysqli_real_escape_string($db_conn, trim($data->user_email));
    if (filter_var($useremail, FILTER_VALIDATE_EMAIL)) {
        $updateUser = mysqli_query($db_conn, "UPDATE `users` SET `user_name`='$username', `user_email`='$useremail' WHERE `id`='$data->id'");
        if ($updateUser) {
            echo json_encode(["success" => 1, "msg" => "User Updated."]);
        } else {
            echo json_encode(["success" => 0, "msg" => "User Not Updated!"]);
        }
    } else {
        echo json_encode(["success" => 0, "msg" => "Invalid Email Address!"]);
    }
} else {
    echo json_encode(["success" => 0, "msg" => "Please fill all the required fields!"]);
}

delete-user.php is obviously for deleting an existing user.

  • POST – /delete-user.php
  • Required Fields – id
<?php
header("Access-Control-Allow-Origin: *");
header("Access-Control-Allow-Headers: access");
header("Access-Control-Allow-Methods: POST");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

require 'db_connection.php';

$data = json_decode(file_get_contents("php://input"));
if (isset($data->id) && is_numeric($data->id)) {
    $delID = $data->id;
    $deleteUser = mysqli_query($db_conn, "DELETE FROM `users` WHERE `id`='$delID'");
    if ($deleteUser) {
        echo json_encode(["success" => 1, "msg" => "User Deleted"]);
    } else {
        echo json_encode(["success" => 0, "msg" => "User Not Found!"]);
    }
} else {
    echo json_encode(["success" => 0, "msg" => "User Not Found!"]);
}

3. All URLs of the API

POST - http://localhost/php-react/add-user.php
GET  - http://localhost/php-react/all-users.php
POST - http://localhost/php-react/update-user.php
POST - http://localhost/php-react/delete-user.php

4. Creating React JS CRUD Application with the PHP API

Demo of React js php crud application

Before we start to create this React JS CRUD application, I suggest you to read this first – Simple React js CRUD application.

First, create a new React CLI Environment called as you wish. Here I named it react-php-crud-app.

In this project, we will use the Fetch API for making the HTTP requests, so you don’t have to install extra packages.

Now, open your react project in your favorite editor, then go to the src folder and delete all the default files and folders because we will start from scratch.

If you are curious to know how many files you need to code to make this application, see the following image –

react-php-crud-app folder structure
react-php-crud-app folder structure
src/
 ├─ components/
 │  ├─ Form.js
 │  ├─ UserList.js
 ├─ Actions.js
 ├─ App.js
 ├─ Context.js
 ├─ index.css
 ├─ index.js

First, at the root of the src folder create a new js file called Context.js, here we will initialize the React JS app Context.

import React from "react";
export const AppContext = React.createContext();
export const Provider = AppContext.Provider;

After that, we will create Actions.js in the same folder (src). This file contains all actions such as – Insert, update, delete, fetch, enable and disable the user edit mode, etc.

import { useEffect, useState } from "react";

export const Actions = () => {
  let [users, setUsers] = useState([]);

    //userLength is for showing the Data Loading message.
  let [userLength, setUserLength] = useState(null);

  useEffect(() => {
    fetch("http://localhost/php-react/all-users.php")
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        if (data.success) {
          setUsers(data.users.reverse());
          setUserLength(true);
        } else {
          setUserLength(0);
        }
      })
      .catch((err) => {
        console.log(err);
      });
  }, []);

  // Inserting a new user into the database.
  const insertUser = (newUser) => {
    fetch("http://localhost/php-react/add-user.php", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(newUser),
    })
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        if (data.id) {
          setUsers([
            {
              id: data.id,
              ...newUser,
            },
            ...users,
          ]);
          setUserLength(true);
        } else {
          alert(data.msg);
        }
      })
      .catch((err) => {
        console.log(err);
      });
  };

  // Enabling the edit mode for a listed user.
  const editMode = (id) => {
    users = users.map((user) => {
      if (user.id === id) {
        user.isEditing = true;
        return user;
      }
      user.isEditing = false;
      return user;
    });
    setUsers(users);
  };

  // Cance the edit mode.
  const cancelEdit = (id) => {
    users = users.map((user) => {
      if (user.id === id) {
        user.isEditing = false;
        return user;
      }
      return user;
    });
    setUsers(users);
  };

  // Updating a user.
  const updateUser = (userData) => {
    fetch("http://localhost/php-react/update-user.php", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(userData),
    })
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        if (data.success) {
          users = users.map((user) => {
            if (user.id === userData.id) {
              user.isEditing = false;
              user.user_name = userData.user_name;
              user.user_email = userData.user_email;
              return user;
            }
            return user;
          });
          setUsers(users);
        } else {
          alert(data.msg);
        }
      })
      .catch((err) => {
        console.log(err);
      });
  };

  // Deleting a user.
  const deleteUser = (theID) => {
      // filter outing the user.
    let userDeleted = users.filter((user) => {
      return user.id !== theID;
    });
    fetch("http://localhost/php-react/delete-user.php", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ id: theID }),
    })
      .then((res) => {
        return res.json();
      })
      .then((data) => {
        if (data.success) {
          setUsers(userDeleted);
          if (users.length === 1) {
            setUserLength(0);
          }
        } else {
          alert(data.msg);
        }
      })
      .catch((err) => {
        console.log(err);
      });
  };

  return {
    users,
    editMode,
    cancelEdit,
    updateUser,
    insertUser,
    deleteUser,
    userLength,
  };
};

Now, we will create our app components. So then, at the root of the src folder, create a new folder called components, and inside this folder we need to create two components – 1) Form.js, 2) UserList.js.

The Form.js is for inserting new users.

import { useState, useContext } from "react";
import { AppContext } from "../Context";
const Form = () => {
  const { insertUser } = useContext(AppContext);
  const [newUser, setNewUser] = useState({});

  // Storing the Insert User Form Data.
  const addNewUser = (e, field) => {
    setNewUser({
      ...newUser,
      [field]: e.target.value,
    });
  };

  // Inserting a new user into the Database.
  const submitUser = (e) => {
    e.preventDefault();
    insertUser(newUser);
    e.target.reset();
  };

  return (
    <form className="insertForm" onSubmit={submitUser}>
      <h2>Insert User</h2>
      <label htmlFor="_name">Name</label>
      <input
        type="text"
        id="_name"
        onChange={(e) => addNewUser(e, "user_name")}
        placeholder="Enter name"
        autoComplete="off"
        required
      />
      <label htmlFor="_email">Email</label>
      <input
        type="email"
        id="_email"
        onChange={(e) => addNewUser(e, "user_email")}
        placeholder="Enter email"
        autoComplete="off"
        required
      />
      <input type="submit" value="Insert" />
    </form>
  );
};

export default Form;

The UserList.js is for displaying all the existing users.

import { useContext, useState } from "react";
import { AppContext } from "../Context";

const UserList = () => {
  const {
    users,
    userLength,
    editMode,
    cancelEdit,
    updateUser,
    deleteUser,
  } = useContext(AppContext);

  // Storing users new data when they editing their info.
  const [newData, setNewData] = useState({});

  const saveBtn = () => {
    updateUser(newData);
  };

  const updateNewData = (e, field) => {
    setNewData({
      ...newData,
      [field]: e.target.value,
    });
  };

  const enableEdit = (id, user_name, user_email) => {
    setNewData({ id, user_name, user_email });
    editMode(id);
  };

  const deleteConfirm = (id) => {
    if (window.confirm("Are you sure?")) {
      deleteUser(id);
    }
  };

  return !userLength ? (
    <p>{userLength === null ? "Loading..." : "Please insert some users."}</p>
  ) : (
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Email</th>
          <th>Action</th>
        </tr>
      </thead>
      <tbody>
        {users.map(({ id, user_name, user_email, isEditing }) => {
          return isEditing === true ? (
            <tr key={id}>
              <td>
                <input
                  type="text"
                  defaultValue={user_name}
                  onChange={(e) => updateNewData(e, "user_name")}
                />
              </td>
              <td>
                <input
                  type="email"
                  defaultValue={user_email}
                  onChange={(e) => updateNewData(e, "user_email")}
                />
              </td>
              <td>
                <button className="btn green-btn" onClick={() => saveBtn()}>
                  Save
                </button>
                <button
                  className="btn default-btn"
                  onClick={() => cancelEdit(id)}
                >
                  Cancel
                </button>
              </td>
            </tr>
          ) : (
            <tr key={id}>
              <td>{user_name}</td>
              <td>{user_email}</td>
              <td>
                <button
                  className="btn default-btn"
                  onClick={() => enableEdit(id, user_name, user_email)}
                >
                  Edit
                </button>
                <button
                  className="btn red-btn"
                  onClick={() => deleteConfirm(id)}
                >
                  Delete
                </button>
              </td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
};

export default UserList;

Again inside the src folder, we need to create last three files – 1) App.js, 2) index.js, 3) index.css.

App.js – Where we combine all the components and providing all the actions through the context.

import { Provider } from "./Context";
import Form from "./components/Form";
import UserList from "./components/UserList";
import { Actions } from "./Actions";
function App() {
  const data = Actions();
  return (
    <Provider value={data}>
      <div className="App">
        <h1>React JS + PHP CRUD Application</h1>
        <div className="wrapper">
          <section className="left-side">
            <Form />
          </section>
          <section className="right-side">
            <UserList />
          </section>
        </div>
      </div>
    </Provider>
  );
}

export default App;

index.js – this is the entry point of the application.

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

index.css

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
    'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background-color: #f7f7f7;
  padding: 50px;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
    monospace;
}

.App {
  max-width: 900px;
  margin: 0 auto;
  background-color: #ffffff;
  padding: 20px;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  border-radius: 3px;
}

.App h1 {
  background-color: #f9f9f9;
  text-align: center;
  padding: 20px;
  margin: 0;
}

.wrapper {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  padding: 20px 0;
}

.left-side {
  width: 30%;
}

.right-side {
  padding-left: 20px;
  width: 70%;
}

.right-side table {
  width: 100%;
  border-collapse: collapse;

}


.right-side table th,
.right-side table td {
  border: 1px solid #cccccc;
  padding: 10px;
  text-align: center;
}

.right-side table td button {
  margin: 5px;
}

.right-side table th {
  background-color: #F9FAFB;
  font-size: 20px;
  padding: 15px;
}

.right-side table button {
  cursor: pointer;
}

.insertForm{
  background-color: #F9FAFB;
  border: 1px solid #cccccc;
  padding: 10px;
}

.insertForm h2 {
  margin: 0 0 10px 0;
  text-align: center;
}

.insertForm label {
  font-weight: bold;
}

.insertForm input {
  width: 100%;
  padding: 10px;
  font-size: 16px;
  margin-bottom: 5px;
}

.insertForm [type="submit"] {
  margin-top: 5px;
  background: #1F2937;
  color: #ffffff;
  border: 1px solid rgba(0, 0, 0, 0.1);
  outline: none;
  border-radius: 2px;
  cursor: pointer;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);

}

.insertForm [type="submit"]:hover {
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}

.btn{
  background: none;
  border: 1px solid rgba(0, 0, 0, 0.1);
  padding: 3px 5px;
  outline: none;
  border-radius: 2px;
}
.red-btn,
.green-btn{
  background: #059669;
  color: #ffffff;
}
.red-btn{
  background: #DC2626;
}
.default-btn{
  background: #E5E7EB;
}
.btn:hover{
  box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}

13 Comments

  1. Doens’t work :/
    My Log after execute: npm start.

    MacBook-Air-de-Guine:react-with-php guinesoftware$ npm start
    npm ERR! code ENOENT
    npm ERR! syscall open
    MacBook-Air-de-Guine:react-with-php guinesoftware$ npm start
    npm ERR! code ENOENT
    npm ERR! syscall open
    npm ERR! path /Users/guinesoftware/Desktop/Estudo/package.json
    npm ERR! errno -2
    npm ERR! enoent ENOENT: no such file or directory, open ‘/Users/guinesoftware/Desktop/Estudo/package.json’
    npm ERR! enoent This is related to npm not being able to find a file.
    npm ERR! enoent
    …….

  2. Thank you very much, sir, finally it worked. Sorry for my last comment. I was using Ampps instead of Xampps.. & That error comes.

    Nicely working now.

  3. hi.
    action.js is very complicated for me.
    other sections are excellent.
    even more complete than w3schools.about react.

Leave a Reply