├── README.md
├── .eslintrc.json
├── public
├── favicon.ico
├── images
│ ├── jeff-tumale-SD9Jyl1xNQ4-unsplash.jpg
│ ├── ryan-plomp-76w_eDO1u1E-unsplash.jpg
│ ├── ryan-plomp-PGTO_A0eLt4-unsplash.jpg
│ ├── ryan-plomp-jvoZ-Aux9aw-unsplash.jpg
│ ├── danilo-capece-NoVnXXmDNi0-unsplash.jpg
│ └── lefteris-kallergis-j1GiPlvSGWI-unsplash.jpg
├── vercel.svg
├── thirteen.svg
└── next.svg
├── jsconfig.json
├── next.config.js
├── src
├── pages
│ ├── _app.js
│ ├── _document.js
│ ├── api
│ │ └── products.js
│ └── index.js
├── lib
│ └── db.js
└── styles
│ ├── base
│ ├── _mixin.scss
│ └── _reset.scss
│ ├── globals.scss
│ └── Home.module.scss
├── .gitignore
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | ## CRUD DEMO for NEXT.JS WITH API ENDPOINT
2 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./src/*"]
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | reactStrictMode: true,
4 | };
5 |
6 | module.exports = nextConfig;
7 |
--------------------------------------------------------------------------------
/public/images/jeff-tumale-SD9Jyl1xNQ4-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/jeff-tumale-SD9Jyl1xNQ4-unsplash.jpg
--------------------------------------------------------------------------------
/public/images/ryan-plomp-76w_eDO1u1E-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/ryan-plomp-76w_eDO1u1E-unsplash.jpg
--------------------------------------------------------------------------------
/public/images/ryan-plomp-PGTO_A0eLt4-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/ryan-plomp-PGTO_A0eLt4-unsplash.jpg
--------------------------------------------------------------------------------
/public/images/ryan-plomp-jvoZ-Aux9aw-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/ryan-plomp-jvoZ-Aux9aw-unsplash.jpg
--------------------------------------------------------------------------------
/public/images/danilo-capece-NoVnXXmDNi0-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/danilo-capece-NoVnXXmDNi0-unsplash.jpg
--------------------------------------------------------------------------------
/src/pages/_app.js:
--------------------------------------------------------------------------------
1 | import "@/styles/globals.scss";
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/lefteris-kallergis-j1GiPlvSGWI-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oelbaga/nextjs-crud-mysql/HEAD/public/images/lefteris-kallergis-j1GiPlvSGWI-unsplash.jpg
--------------------------------------------------------------------------------
/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import { Html, Head, Main, NextScript } from "next/document";
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | mysql_keys
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 | .pnpm-debug.log*
28 |
29 | # local env files
30 | .env*.local
31 |
32 | # vercel
33 | .vercel
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mysql",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@next/font": "13.1.6",
13 | "eslint": "8.34.0",
14 | "eslint-config-next": "13.1.6",
15 | "mysql2": "^3.1.2",
16 | "next": "13.1.6",
17 | "react": "18.2.0",
18 | "react-dom": "18.2.0",
19 | "react-icons": "^4.7.1",
20 | "sass": "^1.58.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/lib/db.js:
--------------------------------------------------------------------------------
1 | import mysql from "mysql2/promise";
2 |
3 | export async function query({ query, values = [] }) {
4 | // PlanetScale;
5 | const dbconnection = await mysql.createConnection(
6 | process.env.MYSQL_DATABASE_URL
7 | );
8 |
9 | //Digital ocean ubuntu
10 | // const dbconnection = await mysql.createConnection({
11 | // host: process.env.MYSQL_HOST,
12 | // database: process.env.MYSQL_DATABASE,
13 | // user: process.env.MYSQL_USER,
14 | // password: process.env.MYSQL_PASSWORD,
15 | // });
16 |
17 | try {
18 | const [results] = await dbconnection.execute(query, values);
19 | dbconnection.end();
20 | return results;
21 | } catch (error) {
22 | throw Error(error.message);
23 | return { error };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/styles/base/_mixin.scss:
--------------------------------------------------------------------------------
1 | @mixin small {
2 | @media only screen and (min-width: 450px) {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin tablet {
8 | @media only screen and (min-width: 768px) {
9 | @content;
10 | }
11 | }
12 |
13 | @mixin desktop {
14 | @media only screen and (min-width: 992px) {
15 | @content;
16 | }
17 | }
18 | @mixin desktoplg {
19 | @media only screen and (min-width: 1200px) {
20 | @content;
21 | }
22 | }
23 | //if needed
24 | // @mixin small_laptop_custom {
25 | // @media only screen and (min-width: 992px) and (max-height: 650px) {
26 | // @content;
27 | // }
28 | // }
29 |
30 | //if custom fonts needed
31 | // @font-face {
32 | // font-family: "AkkuratPro-Light";
33 | // src: url("/fonts/AkkuratPro-Light.otf");
34 | // }
35 | // @font-face {
36 | // font-family: "AkkuratPro-Regular";
37 | // src: url("/fonts/AkkuratPro-Regular.otf");
38 | // }
39 |
--------------------------------------------------------------------------------
/src/styles/globals.scss:
--------------------------------------------------------------------------------
1 | @use "./base/mixin.scss" as mixin;
2 | @import url("https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600&family=Roboto:wght@400;500;700&display=swap");
3 | @import "./base/reset.scss";
4 |
5 | html,
6 | body {
7 | font-family: "Raleway", sans-serif;
8 | padding: 0;
9 | margin: 0;
10 | font-style: normal;
11 | font-weight: 400;
12 | }
13 | h1 {
14 | font-size: 2.2rem;
15 | letter-spacing: 0.03em;
16 | font-weight: 300;
17 | text-transform: uppercase;
18 | letter-spacing: -0.03em;
19 | color: blue;
20 | @include mixin.tablet {
21 | font-size: 5rem;
22 | }
23 | }
24 | h2 {
25 | letter-spacing: -0.03em;
26 | font-weight: 600;
27 | margin-bottom: 1rem;
28 | text-transform: uppercase;
29 | font-size: 1.2rem;
30 | @include mixin.tablet {
31 | font-size: 1.5rem;
32 | }
33 | }
34 | p {
35 | font-size: 1rem;
36 | font-weight: 300;
37 | line-height: 1.2;
38 | margin-top: 1rem;
39 | }
40 | ul li {
41 | font-size: 0.7rem;
42 | @include mixin.tablet {
43 | font-size: 0.8rem;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/thirteen.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/pages/api/products.js:
--------------------------------------------------------------------------------
1 | import { query } from "@/lib/db";
2 |
3 | export default async function handler(req, res) {
4 | let message;
5 | if (req.method === "GET") {
6 | const products = await query({
7 | query: "SELECT * FROM products",
8 | values: [],
9 | });
10 | res.status(200).json({ products: products });
11 | }
12 |
13 | if (req.method === "POST") {
14 | const productName = req.body.product_name;
15 | const addProducts = await query({
16 | query: "INSERT INTO products (product_name) VALUES (?)",
17 | values: [productName],
18 | });
19 | let product = [];
20 | if (addProducts.insertId) {
21 | message = "success";
22 | } else {
23 | message = "error";
24 | }
25 | product = {
26 | product_id: addProducts.insertId,
27 | product_name: productName,
28 | };
29 | res.status(200).json({ response: { message: message, product: product } });
30 | }
31 |
32 | if (req.method === "PUT") {
33 | const productId = req.body.product_id;
34 | const productName = req.body.product_name;
35 | const updateProducts = await query({
36 | query: "UPDATE products SET product_name = ? WHERE product_id = ?",
37 | values: [productName, productId],
38 | });
39 | const result = updateProducts.affectedRows;
40 | if (result) {
41 | message = "success";
42 | } else {
43 | message = "error";
44 | }
45 | const product = {
46 | product_id: productId,
47 | product_name: productName,
48 | };
49 | res.status(200).json({ response: { message: message, product: product } });
50 | }
51 |
52 | if (req.method === "DELETE") {
53 | const productId = req.body.product_id;
54 | const deleteProducts = await query({
55 | query: "DELETE FROM products WHERE product_id = ?",
56 | values: [productId],
57 | });
58 | const result = deleteProducts.affectedRows;
59 | if (result) {
60 | message = "success";
61 | } else {
62 | message = "error";
63 | }
64 | res
65 | .status(200)
66 | .json({ response: { message: message, product_id: productId } });
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/styles/base/_reset.scss:
--------------------------------------------------------------------------------
1 | a,
2 | abbr,
3 | acronym,
4 | address,
5 | applet,
6 | article,
7 | aside,
8 | audio,
9 | b,
10 | big,
11 | blockquote,
12 | body,
13 | canvas,
14 | caption,
15 | center,
16 | cite,
17 | code,
18 | dd,
19 | del,
20 | details,
21 | dfn,
22 | div,
23 | dl,
24 | dt,
25 | em,
26 | fieldset,
27 | figcaption,
28 | figure,
29 | footer,
30 | form,
31 | h1,
32 | h2,
33 | h3,
34 | h4,
35 | h5,
36 | h6,
37 | header,
38 | hgroup,
39 | html,
40 | i,
41 | iframe,
42 | img,
43 | ins,
44 | kbd,
45 | label,
46 | legend,
47 | li,
48 | mark,
49 | menu,
50 | nav,
51 | object,
52 | ol,
53 | p,
54 | pre,
55 | q,
56 | s,
57 | samp,
58 | section,
59 | small,
60 | span,
61 | strike,
62 | strong,
63 | sub,
64 | summary,
65 | sup,
66 | table,
67 | tbody,
68 | td,
69 | tfoot,
70 | th,
71 | thead,
72 | time,
73 | tr,
74 | tt,
75 | u,
76 | ul,
77 | var,
78 | video {
79 | margin: 0;
80 | padding: 0;
81 | font: inherit;
82 | vertical-align: baseline;
83 | }
84 | article,
85 | aside,
86 | details,
87 | figcaption,
88 | figure,
89 | footer,
90 | header,
91 | hgroup,
92 | main,
93 | menu,
94 | nav,
95 | section {
96 | display: block;
97 | position: relative;
98 | }
99 | body {
100 | line-height: 1;
101 | }
102 | ol,
103 | ul {
104 | list-style: none;
105 | }
106 | blockquote,
107 | q {
108 | quotes: none;
109 | }
110 | blockquote:after,
111 | blockquote:before,
112 | q:after,
113 | q:before {
114 | content: "";
115 | content: none;
116 | }
117 | a,
118 | ins {
119 | text-decoration: none;
120 | }
121 | del {
122 | text-decoration: line-through;
123 | }
124 | table {
125 | border-collapse: collapse;
126 | border-spacing: 0;
127 | }
128 | * {
129 | text-rendering: optimizeLegibility;
130 | -moz-osx-font-smoothing: grayscale;
131 | -webkit-tap-highlight-color: transparent;
132 | }
133 | *,
134 | :after,
135 | :before {
136 | box-sizing: border-box;
137 | margin: 0;
138 | padding: 0;
139 | }
140 | textarea,
141 | input,
142 | button,
143 | select {
144 | font-family: inherit;
145 | font-size: inherit;
146 | background: none;
147 | color: inherit;
148 | border: none;
149 | padding: 0;
150 | font: inherit;
151 | cursor: pointer;
152 | outline: inherit;
153 | }
154 | input,
155 | textarea {
156 | cursor: inherit;
157 | }
158 |
159 | a {
160 | color: inherit;
161 | text-decoration: none;
162 | }
163 |
--------------------------------------------------------------------------------
/src/styles/Home.module.scss:
--------------------------------------------------------------------------------
1 | @use "@/styles/base/mixin" as mixin;
2 | .container {
3 | border: 1px solid black;
4 | padding: 1.4rem 0.7rem 2rem;
5 | @include mixin.small {
6 | padding: 1.4rem 1.3rem 2rem;
7 | }
8 | @include mixin.tablet {
9 | padding: 3rem 2rem;
10 | }
11 | a {
12 | text-decoration: inherit;
13 | display: inline-block;
14 | border-bottom: 1px solid black;
15 | &:hover {
16 | background-color: rgb(217, 217, 217);
17 | }
18 | }
19 | .heading {
20 | margin-top: 1rem;
21 | margin-bottom: 1rem;
22 | font-weight: 400;
23 | padding-bottom: 0.3rem;
24 | a {
25 | text-decoration: inherit;
26 | display: inline-block;
27 | border-bottom: 1px solid black;
28 | }
29 | }
30 | footer {
31 | margin: 0 auto;
32 | padding: 1rem 1rem 1rem;
33 | max-width: 800px;
34 | }
35 | section {
36 | margin: 0 auto;
37 | padding: 1rem 1rem 1rem;
38 | max-width: 800px;
39 | border-top: 5px solid black;
40 | @include mixin.small {
41 | padding: 2rem 1rem 2rem;
42 | }
43 | &.main {
44 | border-top: 0px;
45 | padding: 0.5rem 1rem 0.5rem;
46 | @include mixin.tablet {
47 | padding: 0rem 1rem 1rem;
48 | }
49 | }
50 | @include mixin.tablet {
51 | padding: 2.5rem 1rem 2.8rem;
52 | }
53 | .icons {
54 | cursor: pointer;
55 | }
56 | .success {
57 | margin-bottom: 1rem;
58 | font-weight: 700;
59 | color: green;
60 | }
61 | .error {
62 | margin-bottom: 1rem;
63 | font-weight: 700;
64 | color: rgb(175, 3, 3);
65 | }
66 | .products {
67 | height: 100px;
68 | overflow: scroll;
69 | .product {
70 | line-height: 1.5;
71 | margin-bottom: 1rem;
72 | padding-bottom: 0.4rem;
73 | border-bottom: 1px solid grey;
74 | span {
75 | font-weight: 600;
76 | }
77 | img {
78 | display: block;
79 | width: 100px;
80 | border-radius: 10px;
81 | height: 100px;
82 | object-fit: cover;
83 | }
84 | }
85 | }
86 | .input {
87 | width: 100%;
88 | margin-bottom: 1rem;
89 | position: relative;
90 | .label {
91 | margin-bottom: 0.4rem;
92 | display: block;
93 | position: absolute;
94 | left: 10px;
95 | top: -7px;
96 | font-size: 1rem;
97 | background-color: #fff;
98 | padding: 0 5px;
99 | z-index: 1;
100 | font-weight: 300;
101 | }
102 | input,
103 | textarea {
104 | width: 100%;
105 | border-radius: 3px;
106 | border: 1px solid #7a7a7a;
107 | line-height: 2;
108 | padding: 0.5rem 0.3rem 0.5rem 0.5rem;
109 | font-size: 0.8rem;
110 | margin-bottom: 0.3rem;
111 | @include mixin.tablet {
112 | line-height: 2.7;
113 | font-size: 0.9rem;
114 | }
115 | }
116 | textarea {
117 | height: 60px;
118 | }
119 | }
120 | .buttonarea {
121 | input {
122 | width: 100%;
123 | border-radius: 6px;
124 | border: 2px solid blue;
125 | line-height: 2;
126 | padding: 0.5rem 0.3rem;
127 | font-size: 0.9rem;
128 | margin-bottom: 0.3rem;
129 | letter-spacing: 0.2em;
130 | cursor: pointer;
131 | font-weight: 600;
132 | text-transform: uppercase;
133 | -webkit-appearance: none;
134 | -moz-appearance: none;
135 | appearance: none;
136 | @include mixin.tablet {
137 | letter-spacing: 0.56em;
138 | line-height: 2.7;
139 | padding: 0.5rem 0.3rem;
140 | font-size: 0.9rem;
141 | }
142 | &:hover {
143 | background-color: rgb(239, 239, 239);
144 | }
145 | &.warning {
146 | color: white;
147 | border: 2px solid rgb(245, 41, 41);
148 | background-color: rgb(245, 41, 41);
149 | &:hover {
150 | background-color: rgb(210, 8, 8);
151 | }
152 | }
153 | }
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, useRef } from "react";
2 | import { CiTrash } from "react-icons/ci";
3 | import Head from "next/head";
4 | import Image from "next/image";
5 | import styles from "@/styles/Home.module.scss";
6 |
7 | export default function Home() {
8 | const productNameRef = useRef();
9 | const productIDToDeleteRef = useRef();
10 | const productIDToUpdateRef = useRef();
11 | const productNameToUpdateRef = useRef();
12 | const [products, setProducts] = useState([]);
13 | const [updated, setUpdated] = useState(false);
14 | const [updatedError, setUpdatedError] = useState(false);
15 | const [created, setCreated] = useState(false);
16 | const [deleted, setDeleted] = useState(false);
17 | const [deletedError, setDeletedError] = useState(false);
18 |
19 | async function addProduct() {
20 | const productName = productNameRef.current.value.trim();
21 | if (productName.length < 3) return;
22 | const postData = {
23 | method: "POST",
24 | headers: {
25 | "Content-Type": "application/json",
26 | },
27 | body: JSON.stringify({
28 | product_name: productName,
29 | }),
30 | };
31 | if (productName.length < 3) return;
32 | const res = await fetch(
33 | `${process.env.NEXT_PUBLIC_URL}/api/products`,
34 | postData
35 | );
36 | const response = await res.json();
37 | console.log(response);
38 | if (response.response.message !== "success") return;
39 | const newproduct = response.response.product;
40 | setProducts([
41 | ...products,
42 | {
43 | product_id: newproduct.product_id,
44 | product_name: newproduct.product_name,
45 | },
46 | ]);
47 | setCreated(true);
48 | }
49 |
50 | async function getProducts() {
51 | const postData = {
52 | method: "GET",
53 | headers: {
54 | "Content-Type": "application/json",
55 | },
56 | };
57 | const res = await fetch(
58 | `${process.env.NEXT_PUBLIC_URL}/api/products`,
59 | postData
60 | );
61 | const response = await res.json();
62 | setProducts(response.products);
63 | console.log(response);
64 | }
65 |
66 | async function deleteProduct(id) {
67 | if (!id) return;
68 | const postData = {
69 | method: "DELETE",
70 | headers: {
71 | "Content-Type": "application/json",
72 | },
73 | body: JSON.stringify({
74 | product_id: id,
75 | }),
76 | };
77 | const res = await fetch(
78 | `${process.env.NEXT_PUBLIC_URL}/api/products`,
79 | postData
80 | );
81 | const response = await res.json();
82 | console.log(response.response);
83 | if (response.response.message === "error") return setDeletedError(true);
84 | const idToRemove = parseFloat(response.response.product_id);
85 | setProducts(products.filter((a) => a.product_id !== idToRemove));
86 | setDeleted(true);
87 | }
88 |
89 | async function updateProduct() {
90 | const productIDToUpdate = productIDToUpdateRef.current.value.trim();
91 | const productNameToUpdate = productNameToUpdateRef.current.value.trim();
92 | if (!productIDToUpdate.length) return;
93 | const postData = {
94 | method: "PUT",
95 | headers: {
96 | "Content-Type": "application/json",
97 | },
98 | body: JSON.stringify({
99 | product_id: productIDToUpdate,
100 | product_name: productNameToUpdate,
101 | }),
102 | };
103 | const res = await fetch(
104 | `${process.env.NEXT_PUBLIC_URL}/api/products`,
105 | postData
106 | );
107 | const response = await res.json();
108 | if (response.response.message === "error") return setUpdatedError(true);
109 | // if (response.response.message !== "success") return;
110 | const productIdUpdated = parseFloat(response.response.product.product_id);
111 | const productUpdatedName = response.response.product.product_name;
112 | //updating state
113 | const productsStateAfterUpdate = products.map((product) => {
114 | if (product.product_id === productIdUpdated) {
115 | const productUpdated = {
116 | ...product,
117 | product_name: productUpdatedName,
118 | };
119 | return productUpdated;
120 | } else {
121 | return {
122 | ...product,
123 | };
124 | }
125 | });
126 | setUpdated(true);
127 | setProducts(productsStateAfterUpdate);
128 | }
129 |
130 | useEffect(() => {
131 | getProducts();
132 | }, []);
133 |
134 | return (
135 | <>
136 | {" "}
137 |
138 | CRUD With Next.Js & MySQL Demo
139 |
140 |
141 |
142 | CRUD With Next.Js & MySQL Demo
143 | {/*
144 | Create, Read, Update, Delete database data in React, Node, Next.js
145 | and MySQL by Omar Elbaga{" "}
146 |
151 | GitHub
152 |
153 |
*/}
154 |
159 |
160 |
161 |
162 |
Read
163 |
164 | {products.map((item, index) => {
165 | return (
166 |
167 | product_id: {item.product_id}
{" "}
168 | product_name: {item.product_name}{" "}
169 | deleteProduct(item.product_id)}
172 | />
173 |
174 | );
175 | })}
176 | {!products.length ? <>No products found> : null}
177 |
178 |
179 |
180 |
181 |
182 |
Create
183 |
184 |
Product Name
185 |
186 |
187 | {created ?
Success!
: null}
188 |
189 |
195 |
196 |
197 |
198 |
199 |
200 |
Update
201 |
202 |
Product Id
203 |
204 |
205 |
206 |
Product Name
207 |
208 |
209 | {updated ?
Success!
: null}
210 | {updatedError ?
Error!
: null}
211 |
212 |
218 |
219 |
220 |
221 |
222 |
223 |
Delete
224 |
225 |
Product Id
226 |
227 |
228 | {deleted ?
Success!
: null}
229 | {deletedError ?
Error!
: null}
230 |
231 |
236 | deleteProduct(productIDToDeleteRef.current.value)
237 | }
238 | />
239 |
240 |
241 |
242 |
255 |
256 | >
257 | );
258 | }
259 |
--------------------------------------------------------------------------------