Loading your tools...
Loading your tools...
Master React financial math: Convert Hourly to Annual, calculate overtime, estimate taxes, and handle bi-weekly vs. semi-monthly pay periods.
A basic calculator multiplies by 2080. A Professional Salary Calculator understands that "Bi-weekly" is not the same as "Semi-monthly." It knows that overtime pays 1.5x. It knows that "Net Pay" is what actually hits your bank account.
In this guide, we will build a Payroll Engine that handles the messy reality of money.
The biggest mistake beginners make is storing separate states for hourly, weekly, annual, etc.
If you update one, you have to update all of them. Complexity explodes.
Instead, we store ONE value: Hourly Rate.
Everything else (Weekly, Annual, Monthly) is just a math formula based on that one number.
// Good Practice: One Source of Truth
const [hourlyRate, setHourlyRate] = useState(25);
// Derived values (calculated on the fly)
const weekly = hourlyRate * 40;
const annual = hourlyRate * 2080;
These sound the same (twice a month), but they are mathematically different.
Annual / 26Annual / 24This means a semi-monthly paycheck is larger than a bi-weekly one for the same annual salary! Your tool needs to explain this.
Most hourly employees earn "Time and a Half" for hours over 40. If a user works 45 hours/week:
const calculateWeekly = (rate, hours) => {
if (hours <= 40) return rate * hours;
const overtimeHours = hours - 40;
return (40 * rate) + (overtimeHours * rate * 1.5);
};
Exact taxes are impossible without a CPA. But we can estimate Federal Effective Tax Rate. We can use a simplified progressive bracket system logic or a flat estimate table for a "Quick View".
Note: For a simple tool, a "Take Home" slider (estimating 20-30% tax) often provides a good enough UX without complex tax code.
Users type weird things. $25.00, 25,000, 25k.
We need a helper function to strip non-numbers.
const cleanInput = (str) => parseFloat(str.replace(/[^0-9.]/g, '')) || 0;
Don't just use $ + value. Use Intl.NumberFormat. It handles commas, decimals, and symbols automatically.
const formatMoney = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
};
// Output: "$52,000.00"
A simple text list is boring. We can use a CSS-based "Progress Bar" to visualize where the money goes (Taxes vs. Take Home).
<div className="bar-container flex h-4 rounded overflow-hidden">
<div style={{ width: '75%' }} className="bg-green-500"></div> {/* Pay */}
<div style={{ width: '25%' }} className="bg-red-400"></div> {/* Tax */}
</div>
If you are hourly, taking 2 weeks off means losing 2 weeks of pay.
We calculate Adjusted Annual by subtracting hours.
const unpaidHours = weeksOff * 40;
const totalHours = 2080 - unpaidHours;
const adjustedPay = totalHours * hourlyRate;
When users type in "Annual Salary", we need to reverse engineer the Hourly Rate to update our State.
setHourly(Annual / 2080)setHourly(Weekly / 40)This keeps everything perfectly synced.
Here is the complete, advanced component. It handles Overtime, Frequency differences, and Tax Estimation.
import React, { useState } from 'react';
export default function AdvancedSalaryCalculator() {
const [hourly, setHourly] = useState(25);
const [hoursPerWeek, setHoursPerWeek] = useState(40);
const [taxRate, setTaxRate] = useState(22); // Estimate
// Constants
const WEEKS_YEAR = 52;
// Calculations
const overtimeHours = Math.max(0, hoursPerWeek - 40);
const regularHours = Math.min(hoursPerWeek, 40);
const weeklyGross = (regularHours * hourly) + (overtimeHours * hourly * 1.5);
const annualGross = weeklyGross * WEEKS_YEAR;
const annualTax = annualGross * (taxRate / 100);
const annualNet = annualGross - annualTax;
// Format Helper
const fmt = (num) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num);
const handleInputChange = (val, type) => {
const num = parseFloat(val) || 0;
if (type === 'hourly') setHourly(num);
if (type === 'annual') setHourly(num / 2080); // Reverse calc
};
return (
<div className="max-w-2xl mx-auto p-6 bg-white shadow-xl rounded-xl border border-gray-100">
<div className="text-center mb-8">
<h2 className="text-3xl font-bold text-gray-800">💼 Pro Salary Engine</h2>
<p className="text-gray-500">Includes Overtime & Tax Estimates</p>
</div>
{/* Input Section */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<Card title="Base Pay">
<label className="label">Hourly Rate</label>
<input
type="number"
value={hourly.toFixed(2)}
onChange={(e) => handleInputChange(e.target.value, 'hourly')}
className="input-field"
/>
<label className="label mt-4">Hours per Week</label>
<input
type="range" min="1" max="80"
value={hoursPerWeek}
onChange={(e) => setHoursPerWeek(Number(e.target.value))}
className="w-full"
/>
<div className="text-right text-sm font-bold text-blue-600">{hoursPerWeek} Hours</div>
</Card>
<Card title="Tax Estimate">
<label className="label">Estimated Tax %</label>
<input
type="number"
value={taxRate}
onChange={(e) => setTaxRate(Number(e.target.value))}
className="input-field"
/>
<div className="text-xs text-gray-400 mt-2">
Includes Federal, State & FICA estimates.
</div>
<div className="mt-4 p-3 bg-red-50 text-red-700 rounded text-sm">
<strong>Tax Amount:</strong> {fmt(annualTax / 12)} /mo
</div>
</Card>
</div>
{/* Big Results Table */}
<div className="overflow-hidden rounded-lg border border-gray-200">
<table className="w-full text-left border-collapse">
<thead className="bg-gray-50 uppercase text-xs font-semibold text-gray-600">
<tr>
<th className="p-4">Frequency</th>
<th className="p-4">Gross Pay</th>
<th className="p-4 text-green-700">Net Pay (Take Home)</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-100">
<ResultRow label="Weekly" val={weeklyGross} tax={taxRate} />
<ResultRow label="Bi-Weekly (26)" val={weeklyGross * 2} tax={taxRate} />
<ResultRow label="Semi-Monthly (24)" val={annualGross / 24} tax={taxRate} />
<ResultRow label="Monthly" val={annualGross / 12} tax={taxRate} />
<ResultRow label="Annual" val={annualGross} tax={taxRate} highlight />
</tbody>
</table>
</div>
</div>
);
}
// Sub-components for cleaner code
const Card = ({ title, children }) => (
<div className="p-5 bg-gray-50 rounded-lg border border-gray-200">
<h3 className="font-bold text-gray-700 mb-4 uppercase text-xs tracking-wider">{title}</h3>
{children}
</div>
);
const ResultRow = ({ label, val, tax, highlight }) => {
const net = val * (1 - (tax / 100));
const fmt = (n) => new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(n);
return (
<tr className={highlight ? "bg-blue-50 font-bold" : "hover:bg-gray-50"}>
<td className="p-4 text-gray-700">{label}</td>
<td className="p-4 text-gray-500">{fmt(val)}</td>
<td className="p-4 text-green-700">{fmt(net)}</td>
</tr>
);
};
Developer Tools & Resource Experts
FastTools is dedicated to curating high-quality content and resources that empower developers. With nearly 5 years of hands-on development experience, our team rigorously evaluates every tool and API we recommend, ensuring you get only the most reliable and effective solutions for your projects.