├── 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 | 7 | 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 | --------------------------------------------------------------------------------