Loan Charting Territory
I recently added an amortization calculator to my portfolio collection. It was a fun project that helps users visualize loan repayments and see exactly how their money gets split between principal and interest over time. Here's a rundown of how I put it together.
Requirements and Planning
After checking out a bunch of similar calculators online, I sketched out what mine needed to do:
- Let users input basic loan details (how much they're borrowing, interest rate, how long they'll be paying)
- Show a summary with monthly payment, total interest, and total cost
- Create a visual chart showing how payments break down over time
- Provide a detailed payment table they could view by month or year
I wanted everything to update instantly as users played with the numbers. There's something satisfying about seeing how dropping the interest rate even half a percent changes your total interest paid!
User Interface Design
For the layout, I went with a straightforward two-column design:
- Loan input form on the left
- Results on the right (summary stats, chart, and payment table)
This setup creates a natural flow from "what if I borrowed this much?" to "here's what would happen" and works nicely on desktop. For mobile, everything stacks vertically.
The Math Behind the Scenes
The calculations weren't too complex, just your standard loan formulas:
// Figure out the monthly payment
const monthlyRate = annualRate / 100 / 12;
const termMonths = termYears * 12;
const monthlyPayment = (principal * monthlyRate) /
(1 - Math.pow(1 + monthlyRate, -termMonths));
// Build out the payment schedule
let remainingBalance = principal;
for (let month = 1; month <= termMonths; month++) {
const interestPayment = remainingBalance * monthlyRate;
const principalPayment = monthlyPayment - interestPayment;
remainingBalance -= principalPayment;
// Add this payment to our schedule...
}
I had to be a bit careful with the yearly view - adding up 12 months at a time, except for that last partial year that might happen. Also had to watch out for those sneaky rounding errors that can add up after 360 calculations!
State Management in React
React's hooks made state management pretty straightforward:
// The numbers users can change
const [loanAmount, setLoanAmount] = useState(300000);
const [interestRate, setInterestRate] = useState(4.5);
const [loanTerm, setLoanTerm] = useState(30);
// The calculated results
const [amortizationSchedule, setAmortizationSchedule] = useState([]);
const [paymentSummary, setPaymentSummary] = useState({
monthlyPayment: 0,
totalInterest: 0,
totalCost: 0
});
// UI toggles
const [viewMode, setViewMode] = useState('monthly');
Then I just used useEffect to recalculate everything whenever the inputs changed:
useEffect(() => {
const schedule = generateAmortizationSchedule(loanAmount, interestRate, loanTerm);
setAmortizationSchedule(schedule);
const summary = calculatePaymentSummary(schedule, loanAmount);
setPaymentSummary(summary);
}, [loanAmount, interestRate, loanTerm]);
Getting Visual with Recharts
This was my first time using Recharts, and it was pretty intuitive:
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={chartData}>
<XAxis dataKey="paymentNumber" />
<YAxis />
<Tooltip />
<Area type="monotone" dataKey="interest" stackId="1" fill="#8884d8" />
<Area type="monotone" dataKey="principal" stackId="1" fill="#82ca9d" />
</AreaChart>
</ResponsiveContainer>
I had to massage the data a bit to get it into the right format for the chart. Also discovered that ResponsiveContainer can be a bit finicky about its parent's dimensions - took some trial and error to get that right.
The Great Table Space Challenge
One head-scratcher I ran into was getting the amortization table to behave properly. I wanted it to:
After some wrestling with CSS, this approach did the trick:
// In the parent component
<div className="flex flex-col min-h-0">
<div className="flex-shrink-0">
{/* Summary and chart */}
</div>
<div className="flex-1 min-h-0">
{/* AmortizationTable with internal scrolling */}
</div>
</div>
// In the table component
<Card className="flex flex-col flex-1 min-h-0">
<CardHeader className="flex-shrink-0">
{/* Table controls */}
</CardHeader>
<CardContent className="flex-1 min-h-0">
<div className="flex-1 overflow-y-auto min-h-0">
<Table>
{/* Table content */}
</Table>
</div>
</CardContent>
</Card>
That min-h-0 property turned out to be the secret sauce - without it, flex items don't want to shrink below their content size.
Wrapping Up
This project was a nice opportunity to combine several front-end skills:
The finished calculator gives users a clear picture of how their loan payments will break down over time. It's both practical and a good showcase piece for the portfolio. Plus, I might actually use it next time I'm shopping for a mortgage!