import React, { useEffect, useState } from "react";
import { Container, Row } from "react-bootstrap";
import ListCategories from "./ListCategories";
import Hasil from "./Hasil";
import DaftarProduk from "./DaftarProduk";
import axios from "axios";
import keranjang from "../utils/keranjang";
import BASE_URL from "../utils/constata";
const Main = () => {
const [dataProduct, setDataProduct] = useState([]);
const [dataCategories, setDataCategories] = useState([]);
const [categoriesId, setCategoriesId] = useState(1);
const [listPesanan, setListPesanan] = useState([]);
const handleListCategories = (id) => {
setCategoriesId(id);
};
const handleProdukClick = async (produk) => {
keranjang(produk);
};
useEffect(() => {
const getProducts = async () => {
let responseJson = [];
try {
responseJson = await axios.get(
BASE_URL "products?category.id=" categoriesId
);
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataProduct(responseJson.data);
}
};
const getCategories = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "categories");
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataCategories(responseJson.data);
}
};
const getPesanans = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "keranjangs");
} catch (error) {
console.log("Ada yang " error);
} finally {
setListPesanan(responseJson.data);
}
};
getProducts();
getCategories();
getPesanans();
}, [categoriesId]);
return (
<Container className="mt-3">
<Row>
{dataCategories && (
<ListCategories
categories={dataCategories}
handleClick={handleListCategories}
categoriesActive={categoriesId}
></ListCategories>
)}
{dataProduct && (
<DaftarProduk
produk={dataProduct}
handleClick={handleProdukClick}
></DaftarProduk>
)}
<Hasil pesanan={listPesanan}></Hasil>
</Row>
</Container>
);
};
export default Main;
How to update listPesanan
, when handleProdukClick
was click?
if me put listPesanan
inside
useEffect(() => {
const getProducts = async () => {
let responseJson = [];
try {
responseJson = await axios.get(
BASE_URL "products?category.id=" categoriesId
);
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataProduct(responseJson.data);
}
};
const getCategories = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "categories");
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataCategories(responseJson.data);
}
};
const getPesanans = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "keranjangs");
} catch (error) {
console.log("Ada yang " error);
} finally {
setListPesanan(responseJson.data);
}
};
getProducts();
getCategories();
getPesanans();
}, [categoriesId, listPesanan]);
that's causes looping to send request to server
my full code in here
CodePudding user response:
Yes, your second 'solution' will cause looping, because you have listPesanan
in you dependency array (The second parameter to the useEffect function), so whenever listPesanan
changes, the useEffect is run again. And in fact, you are updating the value of listPesanan
in the useEffect, which causes the useEffect to get triggered again, which causes you to update the value of listPesanan, and so on.
If I understand your question/code, a simple solution would just be to declare the getPesanans
function outside of the useEffect
hook, and then have your DaftarProduk
onClick call that function. Then, remove listPesanan from the dependency array of useEffect
. Your code would look like this:
import React, { useEffect, useState } from "react";
import { Container, Row } from "react-bootstrap";
import ListCategories from "./ListCategories";
import Hasil from "./Hasil";
import DaftarProduk from "./DaftarProduk";
import axios from "axios";
import keranjang from "../utils/keranjang";
import BASE_URL from "../utils/constata";
const Main = () => {
const [dataProduct, setDataProduct] = useState([]);
const [dataCategories, setDataCategories] = useState([]);
const [categoriesId, setCategoriesId] = useState(1);
const [listPesanan, setListPesanan] = useState([]);
**const getPesanans = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "keranjangs");
} catch (error) {
console.log("Ada yang " error);
} finally {
setListPesanan(responseJson.data);
}
};**
const handleListCategories = (id) => {
setCategoriesId(id);
};
const handleProdukClick = async (produk) => {
keranjang(produk);
**await getPesanans();**
};
useEffect(() => {
const getProducts = async () => {
let responseJson = [];
try {
responseJson = await axios.get(
BASE_URL "products?category.id=" categoriesId
);
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataProduct(responseJson.data);
}
};
const getCategories = async () => {
let responseJson = [];
try {
responseJson = await axios.get(BASE_URL "categories");
} catch (error) {
console.log("Ada yang " error);
} finally {
setDataCategories(responseJson.data);
}
};
getProducts();
getCategories();
getPesanans();
}, [categoriesId]);
return (
<Container className="mt-3">
<Row>
{dataCategories && (
<ListCategories
categories={dataCategories}
handleClick={handleListCategories}
categoriesActive={categoriesId}
></ListCategories>
)}
{dataProduct && (
<DaftarProduk
produk={dataProduct}
handleClick={handleProdukClick}
></DaftarProduk>
)}
<Hasil pesanan={listPesanan}></Hasil>
</Row>
</Container>
);
};
export default Main;
CodePudding user response:
problem
First handleProdukClick
triggers another effect, keranjang
.
const handleProdukClick = async (produk) => { // ⚠️ async misuse
keranjang(produk); // ⚠️ effect
// ✏️ update listPesanan here ...
};
code review
Your linked source code shows this implementation for keranjang
. This code misunderstands how to effectively apply async
and await
. Let's fix that first.
- ⚠️
await..then
anti-pattern doesn't assign result to variable - ⚠️ 38-LOC function with mixed concerns
- ⚠️ Errors are swallowed so caller cannot respond to them
const keranjang = async (produk) => {
let pesanan = {
jumlah: 1,
total_harga: produk.harga,
product: produk,
};
const popUpPesanan = () => // ⚠️ nested function, no arguments
swal({
title: "Berhasil",
text: "Masuk Keranjang " produk.nama,
icon: "success",
button: "Oke",
});
await axios // ⚠️ no return
.get(BASE_URL "keranjangs?product.id=" produk.id)
.then(async (res) => {
if (res.data.length === 0) {
await axios // ⚠️ no return
.post(BASE_URL "keranjangs", pesanan)
.then(() => popUpPesanan())
.catch((error) => console.log(error)); // ⚠️ swallow error
} else {
pesanan = { // ⚠️ variable reassignment
jumlah: res.data[0].jumlah 1,
total_harga: res.data[0].total_harga produk.harga,
product: produk,
};
await axios // ⚠️ no return
.put(BASE_URL "keranjangs/" res.data[0].id, pesanan)
.then(() => popUpPesanan())
.catch((error) => console.log(error)); // ⚠️ swallow error
}
})
.catch((error) => console.log(error)); // ⚠️ swallow error
};
First write keranjang
from a high-level.
- ✅ Reusable
get
,add
, andincrease
functions decouple separate concerns - ✅ Return
Promise
sohandleProduckClick
can know when it is complete - ✅ Pass arguments to functions instead of reassigning variables
- ✅ Do not swallow error messages with
.catch
const keranjang = async (produk) => {
const pesanan = await get(produk.id)
if (pesanan.length === 0)
return add(produk).then(() => popUpPesanan(produk))
else
return increase(pesanan[0], produk).then(() => popUpPesanan(produk))
}
- ✅ Implement
get
,add
,increase
- ✅ Each function returns a
Promise
const get = (id) =>
axios.get(`${BASE_URL}keranjangs?product.id=${id}`).then(res => res.data)
const add = (produk) =>
axios.post(`${BASE_URL}keranjangs`, {
jumlah: 1,
total_harga: produk.harga,
product: produk,
})
const increase = (pesanan, produk) =>
axios.put(`${BASE_URL}keranjangs/${pesanan.id}`, {
jumlah: pesanan.jumlah 1,
total_harga: pesanan.total_harga produk.harga,
product: produk,
})
popUpPesanan
accepts aproduk
argument
const popUpPesanan = (produk) =>
swal({
title: "Berhasil",
text: "Masuk Keranjang " produk.nama,
icon: "success",
button: "Oke",
})
fix #1
With all of that fixed, handleProdukClick
can appropriately respond to the keranjang
call.
const handleProdukClick = async (produk) => {
try {
await keranjang(produk) // ✅ await
setListPesanan(await getPesanans())// ✅ await and set
} catch (err) {
console.log(err) // ✅ caller handles error
}
};
You don't need async
and await
for this. It's easier to write
const handleProdukClick = (produk) => {
keranjang(produk).then(setListPesanan).catch(console.error)
}
fix #2
Now you have to move getPesanans
out of the useEffect
call in your component and decouple the mixed concerns like you did with keranjang
...
import { getProducts, getCategories, getPesanans } from "./api"
const Main = () => {
const [dataProduct, setDataProduct] = useState([])
const [dataCategories, setDataCategories] = useState([])
const [categoriesId, setCategoriesId] = useState(1)
const [listPesanan, setListPesanan] = useState([])
const handleListCategories = (id) => {
setCategoriesId(id)
}
const handleProdukClick = (produk) => {
keranjang(produk).then(setListPesanan).catch(console.error)
}
useEffect(() => {
getProducts(categoriesId).then(setDataProduct).catch(console.error)
getCategories().then(setDataCategories).catch(console.error)
getPesanans().then(setListPesanan).catch(console.error)
}, [categoriesId]) // ✅ no infinite loop
return (...)
}
api
Define a reusable api
module so each component does not need to concern itself with axios
, building URLs, or picking apart responses.
- ✅ Reusable functions separate concerns
- ✅
.then(res => res.data)
allows caller to access data directly - ✅ Errors are not swallowed
- ✅
axios.create
makes instance so you don't have to addBASE_URL
to everything
import axios from "axios"
import BASE_URL from "../utils/constata";
const client = axios.create({ baseURL: BASE_URL })
const getProducts = (categoriesId) =>
client.get("/products?category.id=" categoriesId).then(res => res.data)
const getCategories = () =>
client.get("/categories").then(res => res.data)
const getPesanans = () =>
client.get("/keranjangs").then(res => res.data)
export { getProducts, getCategories, getPesanans }
homework
✏️ Move
get
,add
, andincrease
functions you wrote inkeranjang
to theapi
module.✏️ Remove unnecessary
BASE_URL
✏️ Rename them accordingly and add them to the
export
list✏️ Any time you see
axios
spilling into your other components, refactor and move them to yourapi
module✏️ Consider using
transformRequest
andtransformResponse
in your axios config so you don't have to add.then(res => res.data)
to each request✏️ Consider decoupling
swal
andkeranjang
.keranjang
can move to theapi
module andswal
can be called from your component.// api.js const keranjang = async (produk) => { const pesanan = await get(produk.id) if (pesanan.length === 0) return add(produk) else return increase(pesanan[0], produk) }
// component.js const Main = () => { // ... const handleProdukClick = (produk) => { keranjang(produk) .then(setListPesanan) .then(_ => swal({ title: "Berhasil", text: "Masuk Keranjang " produk.nama, icon: "success", button: "Oke", })) .catch(console.error) } // ... }
✏️
util/keranjang
is now an empty module and can be removed