Masukkan Password
70 Hitung Saldo Pengeluaran
Lecture ini sangat penting karena sekarang Expense Tracker kita bukan hanya bisa menyimpan dan menampilkan expense, tetapi juga mulai bisa menghitung saldo/available balance.
Kalau lecture sebelumnya fokus pada:
Expense → HTML → UI
maka lecture ini menambahkan:
Expense → perhitungan balance → tampilkan balance ke UI
Konsep TypeScript/JavaScript yang paling penting di sini adalah reduce().
1. Apa yang ingin kita buat?
Misalnya user memasukkan:
Salary Credit 10,000
Groceries Debit 250
Movie Ticket Debit 150
YouTube Income Credit 500
Kita ingin menghitung:
10,000 - 250 - 150 + 500
= 10,100
Kemudian UI menampilkan:
Available Balance
10,100
Jadi aturan perhitungannya:
CREDIT → tambah
DEBIT → kurang
Secara matematika:
Balance = Total Credit - Total Debit
2. Membuat variable totalAmount
Lecture membuat global variable:
let totalAmount: number = 0;
Artinya:
totalAmount
↓
menyimpan saldo saat ini
Awalnya:
totalAmount = 0
Setelah ada credit:
totalAmount = 10000
Setelah debit 250:
totalAmount = 9750
Setelah debit 150:
totalAmount = 9600
Dan seterusnya.
3. Kenapa totalAmount bertipe number?
Karena kita melakukan operasi matematika:
10_000 + 500 - 250
Hasilnya adalah:
number
Jadi:
let totalAmount: number = 0;
sangat masuk akal.
Kalau kita menulis:
let totalAmount: string = "0";
akan bermasalah ketika ingin melakukan perhitungan seperti:
totalAmount += 100;
Karena kita sebenarnya ingin bekerja dengan angka.
4. Membuat function calculateTotal()
Sekarang dibuat function:
function calculateTotal() {
// menghitung total
}
Tujuannya:
Mengambil semua expense dari
expenseItems, lalu menghitung balance berdasarkancreditdandebit.
Ingat array kita:
const expenseItems: Expense[] = [];
Misalnya:
expenseItems
│
├── Salary credit 10000
├── Groceries debit 250
├── Movie debit 150
└── YouTube credit 500
calculateTotal() harus menghasilkan:
10100
5. Kenapa menggunakan reduce()?
Ini inti utama lecture.
reduce() digunakan ketika kita punya banyak data, tetapi ingin menghasilkan satu nilai akhir.
Contoh sederhana:
const numbers = [10, 20, 30, 40];
Kita ingin:
10 + 20 + 30 + 40
hasilnya:
100
Array:
[10, 20, 30, 40]
berubah menjadi satu nilai:
100
Itulah salah satu kegunaan utama reduce().
6. Cara berpikir reduce()
Bayangkan kita mempunyai:
[10, 20, 30]
Kita mulai dengan total:
0
Kemudian:
0 + 10 = 10
10 + 20 = 30
30 + 30 = 60
Hasil akhir:
60
Jadi reduce() bekerja seperti:
array
↓
item pertama → accumulator
↓
item kedua → accumulator
↓
item ketiga → accumulator
↓
hasil akhir
7. Bentuk dasar reduce()
Bentuk sederhananya:
const result = numbers.reduce(
(accumulator, item) => {
return accumulator + item;
},
0
);
Ada dua bagian penting:
(accumulator, item) => {
...
}
dan:
0
8. Apa itu accumulator?
Accumulator adalah nilai yang dikumpulkan dari iterasi sebelumnya.
Dalam lecture diberi nama:
accumulatorTotal
Misalnya:
array = [10, 20, 30]
dan initial value:
0
Maka:
Iterasi 1
accumulator = 0
item = 10
hasil:
0 + 10 = 10
Accumulator menjadi:
10
Iterasi 2
accumulator = 10
item = 20
hasil:
10 + 20 = 30
Accumulator:
30
Iterasi 3
accumulator = 30
item = 30
hasil:
30 + 30 = 60
Hasil akhir:
60
9. Apa itu item?
Parameter kedua:
item
adalah element yang sedang diproses.
Dalam project kita:
expenseItems
berisi:
Expense
Expense
Expense
Expense
Jadi setiap iterasi:
exp
adalah satu object Expense.
Misalnya:
{
type: "credit",
description: "Salary",
amount: 10000
}
10. Initial value 0
Lecture memberikan:
expenseItems.reduce(
(accumulatorTotal, exp) => {
...
},
0
);
Angka:
0
di bagian terakhir adalah initial value untuk accumulator.
Artinya:
Sebelum mulai menghitung, total kita mulai dari
0.
Secara visual:
Initial total
↓
0
↓
Expense #1
↓
Expense #2
↓
Expense #3
↓
Final total
11. Sekarang masalahnya: Credit dan Debit berbeda
Kalau semua expense hanya credit:
10000
500
2000
kita bisa:
10000 + 500 + 2000
Tetapi debit harus dikurangi.
Misalnya:
Salary credit 10000
Groceries debit 250
Movie debit 150
YouTube credit 500
Kita ingin:
10000 - 250 - 150 + 500
Jadi sebelum memasukkan amount ke total, kita harus menentukan:
credit → +amount
debit → -amount
12. Mengambil amount dari Expense
Di dalam reduce():
const amount = exp.amount;
Misalnya:
exp = {
type: "credit",
description: "Salary",
amount: 10000
};
maka:
amount = 10000;
Kalau:
exp = {
type: "debit",
description: "Groceries",
amount: 250
};
maka:
amount = 250;
Tetapi kita belum menentukan apakah 250 harus menjadi:
+250
atau:
-250
13. Mengecek expense.type
Lecture melakukan:
if (exp.type === "debit") {
amount = -amount;
}
Ini adalah logika yang sangat sederhana tetapi penting.
Kalau:
type = credit
kita tidak mengubah amount.
Misalnya:
amount = 10000
tetap:
+10000
Kalau:
type = debit
kita ubah:
250
menjadi:
-250
14. Kenapa cukup mengecek debit?
Karena kita sudah tahu type hanya bisa:
"credit" | "debit"
Jadi kalau bukan:
"debit"
berarti:
"credit"
Maka:
if (exp.type === "debit") {
amount = -amount;
}
artinya:
debit → negatif
credit → tetap positif
15. Setelah itu tambahkan ke total
Lecture menggunakan:
total += amount;
Ini sama dengan:
total = total + amount;
Misalnya:
total = 10000
amount = -250
maka:
total += amount
menjadi:
10000 + (-250)
= 9750
16. Simulasi reduce() dengan Expense
Mari kita lakukan step-by-step.
Data:
Salary credit 10000
Groceries debit 250
Movie debit 150
YouTube credit 500
Initial:
total = 0
Iterasi 1 — Salary
type = credit
amount = 10000
Credit → positif.
total = 0 + 10000
hasil:
10000
Iterasi 2 — Groceries
type = debit
amount = 250
Debit → negatif:
amount = -250
Kemudian:
total = 10000 + (-250)
hasil:
9750
Iterasi 3 — Movie
type = debit
amount = 150
menjadi:
-150
Kemudian:
9750 + (-150)
hasil:
9600
Iterasi 4 — YouTube
type = credit
amount = 500
tetap:
+500
Kemudian:
9600 + 500
hasil:
10100
Jadi:
Final total = 10,100
17. Bentuk function-nya
Secara konsep, function lecture menjadi:
function calculateTotal(): number {
return expenseItems.reduce(
(total, exp) => {
let amount = exp.amount;
if (exp.type === "debit") {
amount = -amount;
}
total += amount;
return total;
},
0
);
}
Perhatikan bagian penting:
return total;
18. Kenapa harus return total?
Ini salah satu bagian yang sempat menyebabkan error di lecture.
reduce() harus menghasilkan nilai accumulator untuk iterasi berikutnya.
Misalnya:
return total;
Tanpa return, callback tidak mengembalikan nilai yang diharapkan.
Selain itu, function:
calculateTotal()
juga perlu mengembalikan hasil akhirnya.
Jadi ada dua level konsep:
reduce callback
↓
return total
↓
reduce menghasilkan final total
↓
calculateTotal()
↓
return hasil reduce
19. Kenapa calculateTotal() harus mengembalikan number?
Kita memiliki:
let totalAmount: number = 0;
Kemudian:
totalAmount = calculateTotal();
Artinya TypeScript mengharapkan:
calculateTotal() → number
Kalau function tidak melakukan:
return ...
TypeScript bisa menganggap function tersebut:
void
Kemudian terjadi masalah:
void
↓
tidak bisa dimasukkan ke
↓
number
Makanya lecture menambahkan:
return total;
20. Setelah menghitung, simpan ke totalAmount
Ketika user klik Add Expense:
expenseItems.push(expense);
Kemudian:
totalAmount = calculateTotal();
Jadi flow-nya:
User klik Add Expense
↓
buat Expense
↓
push ke expenseItems
↓
calculateTotal()
↓
hitung semua Expense
↓
return total
↓
totalAmount = hasil
21. Kenapa menghitung ulang seluruh array?
Karena calculateTotal() mengambil seluruh:
expenseItems
Jadi setiap kali ada perubahan:
[Salary]
hitung:
10000
Kemudian menjadi:
[Salary, Groceries]
hitung ulang:
10000 - 250
= 9750
Kemudian:
[Salary, Groceries, Movie]
hitung lagi:
10000 - 250 - 150
= 9600
Ini membuat totalAmount selalu mencerminkan data terbaru.
22. Sekarang kita sudah punya data total
Misalnya:
totalAmount = 10100;
Tetapi user belum melihatnya.
Ingat:
Data ≠ UI
Kita punya:
totalAmount
di TypeScript.
Tetapi perlu memasukkannya ke HTML.
23. Mengambil Total Amount Div
Di HTML sudah ada element untuk total.
Misalnya:
<div class="total-expense-amount">
...
</div>
Kita akses:
const totalAmountDiv = document.querySelector(
".total-expense-amount"
) as HTMLDivElement;
Ini sama seperti lecture sebelumnya ketika kita mengambil:
debitDiv
creditDiv
24. Kenapa as HTMLDivElement?
Karena kita ingin TypeScript mengetahui:
totalAmountDiv
adalah <div>.
Dengan:
as HTMLDivElement
kita bisa menggunakan property seperti:
totalAmountDiv.innerHTML
atau:
totalAmountDiv.textContent
25. Membuat function showTotal()
Kemudian dibuat:
function showTotal() {
// tampilkan total
}
Tujuannya:
Mengambil nilai
totalAmountdan menampilkannya ke HTML.
26. Menggunakan textContent
Lecture menggunakan:
totalAmountDiv.textContent = totalAmount.toString();
Mari kita pecah.
totalAmount
Misalnya:
10100
Ini adalah:
number
Sedangkan:
textContent
digunakan untuk teks.
Jadi kita ubah:
number
menjadi:
string
dengan:
.toString()
27. Apa fungsi .toString()?
Misalnya:
const amount = 10000;
type:
number
Kemudian:
amount.toString()
hasil:
"10000"
Sekarang type-nya:
string
Jadi:
totalAmountDiv.textContent = totalAmount.toString();
berarti:
number
↓
toString()
↓
string
↓
textContent
28. Kenapa tidak langsung?
Mungkin kamu bertanya:
totalAmountDiv.textContent = totalAmount;
Kenapa tidak?
Karena textContent secara TypeScript digunakan untuk nilai teks:
string | null
sementara:
totalAmount
adalah:
number
Jadi lecture mengubahnya:
totalAmount.toString()
29. Catatan penting tentang textContent
textContent dapat berupa:
string | null
karena secara DOM nilainya bisa saja null.
Tetapi ketika kita mengisi:
element.textContent = "10000";
kita memberikan string, jadi aman.
Yang penting di sini adalah memahami:
totalAmount → number
textContent → text
maka kita lakukan konversi:
number → string
30. Jangan lupa memanggil showTotal()
Sama seperti lecture sebelumnya, membuat function saja tidak cukup.
Kalau kita hanya membuat:
function showTotal() {
totalAmountDiv.textContent =
totalAmount.toString();
}
tetapi tidak pernah memanggil:
showTotal();
maka tidak ada perubahan pada UI.
Jadi setelah:
totalAmount = calculateTotal();
kita perlu:
showTotal();
31. Flow lengkap ketika user menambahkan expense
Sekarang flow aplikasi menjadi:
User mengisi form
↓
Klik Add Expense
↓
new Expense(...)
↓
expenseItems.push(expense)
↓
calculateTotal()
↓
hitung credit/debit
↓
totalAmount = hasil
↓
displayExpenseItems()
↓
showTotal()
↓
UI diperbarui
Jadi sekarang satu klik melakukan dua jenis update UI:
1. Update daftar expense
2. Update balance
32. Contoh nyata
Misalnya awal:
Available Balance
0
User menambahkan:
Salary
Credit
10000
Data:
expenseItems = [
Salary → credit → 10000
]
calculateTotal():
0 + 10000
= 10000
Kemudian:
totalAmount = 10000;
showTotal():
Available Balance
10000
33. Tambah debit
User menambahkan:
Groceries
Debit
250
Array:
[
Salary → credit → 10000
Groceries → debit → 250
]
Calculate:
10000 - 250
= 9750
UI:
Available Balance
9750
34. Tambah debit lagi
Movie Ticket
Debit
150
Array:
[
Salary
Groceries
Movie Ticket
]
Perhitungan:
10000 - 250 - 150
= 9600
UI:
Available Balance
9600
35. Tambah credit lagi
YouTube Income
Credit
500
Perhitungan:
10000
- 250
- 150
+ 500
= 10100
UI:
Available Balance
10100
36. Kenapa konsep reduce() sangat cocok di sini?
Karena kita punya:
BANYAK Expense
tetapi ingin:
SATU total balance
Ini persis pola:
Many → One
Contoh:
Expense 1 ─┐
Expense 2 ─┤
Expense 3 ─┼──→ reduce() ──→ total
Expense 4 ─┘
Jadi cara mudah mengingat reduce():
reduce()= mengubah banyak data menjadi satu hasil.
37. Bedanya map(), filter(), dan reduce()
Ini bagus untuk mulai kamu kenali karena nanti sering dipakai.
map()
Mengubah setiap item menjadi item baru.
[1, 2, 3]
↓ map
[2, 4, 6]
Many → Many
filter()
Memilih sebagian item.
[1, 2, 3, 4]
↓ filter > 2
[3, 4]
Many → Many (lebih sedikit)
reduce()
Menggabungkan banyak item menjadi satu hasil.
[1, 2, 3, 4]
↓ reduce
10
Many → One
Untuk Expense Tracker:
Expense[]
↓ reduce()
number
Ini sangat cocok.
38. reduce() bisa dibayangkan seperti mesin penghitung
Bayangkan:
┌───────────────────────────────┐
│ REDUCE MACHINE │
│ │
│ Start = 0 │
│ │
│ Expense 1 → +10000 │
│ Expense 2 → -250 │
│ Expense 3 → -150 │
│ Expense 4 → +500 │
│ │
│ Result = 10100 │
└───────────────────────────────┘
Jadi accumulator adalah seperti saldo sementara.
39. Hubungan total dan amount
Dalam reduce:
(total, exp) => {
let amount = exp.amount;
if (exp.type === "debit") {
amount = -amount;
}
total += amount;
return total;
}
Kita punya dua angka yang berbeda fungsi.
amount
Amount untuk expense yang sedang diproses.
Salary → amount = 10000
Groceries → amount = 250
total
Saldo yang sudah terkumpul sampai saat ini.
0
10000
9750
9600
10100
Ini penting jangan sampai tertukar.
40. Visualisasi reduce
expenseItems
│
┌────────────────┼────────────────┐
↓ ↓ ↓
Salary Groceries Movie
+10000 -250 -150
│ │ │
└────────────────┴────────────────┘
↓
total
↓
9600
Lebih detail:
START
│
↓
total = 0
│
↓
+ 10000
│
↓
total = 10000
│
↓
- 250
│
↓
total = 9750
│
↓
- 150
│
↓
total = 9600
│
↓
+ 500
│
↓
total = 10100
41. Kenapa totalAmount dibuat global?
Lecture membuat:
let totalAmount: number = 0;
di luar function.
Kemudian beberapa function bisa menggunakannya:
calculateTotal()
↓
menghitung
totalAmount
↑
menyimpan hasil
showTotal()
↓
menampilkan
Jadi:
calculateTotal
↓
totalAmount
↓
showTotal
totalAmount menjadi semacam shared state sederhana untuk aplikasi kita.
42. Tapi sebenarnya calculateTotal() sendiri sudah punya hasil
Kita sebenarnya bisa mendapatkan:
const total = calculateTotal();
Jadi kenapa ada:
totalAmount
global?
Karena lecture sedang membuat state sederhana yang dapat digunakan oleh bagian lain dari aplikasi.
Nanti ketika expense dihapus, misalnya:
Expense dihapus
↓
calculateTotal()
↓
totalAmount diperbarui
↓
showTotal()
Konsep ini akan semakin berguna pada lecture berikutnya.
43. Hubungan dengan lecture sebelumnya
Kita sekarang memiliki dua function utama untuk UI.
displayExpenseItems()
Mengurus:
Expense list
showTotal()
Mengurus:
Available balance
Jadi:
expenseItems
│
├──────────────→ displayExpenseItems()
│ ↓
│ UI list
│
└──────────────→ calculateTotal()
↓
totalAmount
↓
showTotal()
↓
balance UI
Ini sudah mulai terlihat seperti struktur aplikasi sungguhan.
44. Ada pola penting: data sebagai source of truth
Sekarang:
expenseItems
adalah sumber data utama.
Dari array itu kita bisa mendapatkan:
Expense list
displayExpenseItems();
Total balance
calculateTotal();
Jadi:
expenseItems
│
┌─────────┴─────────┐
↓ ↓
displayExpenseItems calculateTotal
↓ ↓
Expense List totalAmount
↓
showTotal()
↓
Balance UI
Ini konsep yang sangat penting dalam frontend.
45. Kenapa balance harus dihitung dari expenseItems?
Karena jangan mengandalkan angka yang hanya ada di UI.
Misalnya UI menampilkan:
10000
Tetapi sebenarnya array memiliki:
Salary +10000
Groceries -250
Movie -150
Maka UI harus mengikuti data.
Yang benar:
expenseItems
↓
calculate
↓
10000
↓
UI
Bukan:
UI
↓
tebak total
Jadi:
Data adalah sumber kebenaran, UI adalah representasinya.
46. Perubahan kecil: “Available Balance”
Lecture juga mengubah UI agar tidak hanya menampilkan:
10000
tetapi:
Available Balance
10000
Ini lebih jelas untuk user.
Secara HTML mungkin ada:
<div>
<div>Available Balance</div>
<div class="total-expense-amount"></div>
</div>
Kemudian TypeScript mengisi bagian kedua:
totalAmountDiv.textContent =
totalAmount.toString();
47. Connection dengan type safety
Perhatikan bagaimana TypeScript membantu kita di lecture ini.
totalAmount
let totalAmount: number = 0;
Jadi TypeScript tahu:
totalAmount harus number
Expense amount
amount: number
Jadi:
exp.amount
adalah number.
Expense type
type: "credit" | "debit"
Jadi kita bisa dengan aman melakukan:
if (exp.type === "debit")
DOM
as HTMLDivElement
memberi tahu TypeScript bahwa element adalah <div>.
Jadi beberapa konsep TypeScript sebelumnya sekarang digunakan bersama-sama.
48. Code inti lecture
Secara keseluruhan, bagian baru yang dipelajari kira-kira seperti ini:
let totalAmount: number = 0;
function calculateTotal(): number {
return expenseItems.reduce(
(total, exp) => {
let amount = exp.amount;
if (exp.type === "debit") {
amount = -amount;
}
total += amount;
return total;
},
0
);
}
Kemudian DOM:
const totalAmountDiv = document.querySelector(
".total-expense-amount"
) as HTMLDivElement;
Function:
function showTotal() {
totalAmountDiv.textContent =
totalAmount.toString();
}
Dan ketika expense ditambahkan:
expenseItems.push(expense);
totalAmount = calculateTotal();
displayExpenseItems();
showTotal();
49. Sebenarnya reduce() di sini bisa dibuat lebih pendek
Setelah kamu benar-benar memahami versi lecture, bentuknya bisa disederhanakan.
Misalnya:
function calculateTotal(): number {
return expenseItems.reduce((total, exp) => {
const amount =
exp.type === "debit"
? -exp.amount
: exp.amount;
return total + amount;
}, 0);
}
Atau bahkan:
function calculateTotal(): number {
return expenseItems.reduce(
(total, exp) =>
total +
(exp.type === "debit"
? -exp.amount
: exp.amount),
0
);
}
Tetapi untuk pemula, versi lecture lebih mudah dipahami karena setiap langkah terlihat jelas.
50. Satu hal yang perlu kamu pahami tentang reduce()
Jangan menghafalnya sebagai:
“
reduceadalah function untuk menjumlahkan array.”
Itu terlalu sempit.
Lebih tepat:
reduce()adalah method untuk memproses banyak item dan menghasilkan satu nilai akhir.
Contohnya bisa menghasilkan:
number
string
object
array
boolean
tergantung bagaimana kita menulis reducer-nya.
Dalam project ini:
Expense[]
↓
reduce()
↓
number
51. Cheat Sheet Lecture
| Konsep | Arti |
|---|---|
totalAmount | menyimpan balance |
number | tipe data angka |
reduce() | mengubah banyak item menjadi satu hasil |
accumulator | hasil sementara |
exp | Expense yang sedang diproses |
initial value = 0 | total dimulai dari 0 |
exp.amount | mengambil nominal expense |
credit | amount positif |
debit | amount negatif |
total += amount | tambahkan amount ke total |
return total | kirim accumulator ke iterasi berikutnya |
textContent | mengisi teks pada element |
.toString() | mengubah number menjadi string |
showTotal() | menampilkan balance ke UI |
52. Cara mengingat reduce()
Pakai kalimat ini:
“Saya punya banyak Expense, tetapi saya ingin satu total.”
Maka:
expenseItems.reduce(...)
Dan ingat:
Many → One
Sedangkan:
map → Many → Many
filter → Many → Many (lebih sedikit)
reduce → Many → One
53. Inti seluruh lecture
Lecture ini sebenarnya hanya punya tiga tahap besar:
1. Hitung balance
expenseItems
↓
reduce()
↓
totalAmount
Dengan aturan:
credit → +
debit → -
2. Simpan hasil
totalAmount = calculateTotal();
3. Tampilkan hasil ke UI
showTotal();
Jadi keseluruhan flow:
expenseItems
│
↓
calculateTotal()
│
↓
┌────────────────────┐
│ reduce() │
│ │
│ credit → +amount │
│ debit → -amount │
└─────────┬──────────┘
↓
totalAmount
│
↓
showTotal()
│
↓
DOM
│
↓
UI
Dan ketika user menambahkan expense:
Add Expense
↓
Create Expense
↓
push ke expenseItems
↓
calculateTotal()
↓
displayExpenseItems()
↓
showTotal()
↓
UI terbaru
Jadi sekarang aplikasi kita sudah punya dua output dari satu sumber data: daftar expense dan available balance. Lecture berikutnya menjadi sangat menarik karena ketika user menekan tombol X/delete, kita harus menghapus expense dari expenseItems, lalu menghitung ulang balance dan me-render UI lagi.