📘 [Recat] React 핵심 - 연습 프로젝트

📘 [Recat] React 핵심 - 연습 프로젝트

[Recat] React 핵심 - 연습 프로젝트

연습 프로젝트는 Investment Calculator을 구현해본다.

src/components/Header.jsx 파일 생성

Header 컴포넌트 작성

// Header.jsx
// logo 이미지를 불러온다.
import logo from '../assets/investment-calculator-logo.png';

export default function Header() {
  return (
    <header id="header">
      <img src={logo} alt="Logo showing a money bag" />
      <h1>Investment Calculator</h1>
    </header>
  );
}

App.jsx에서 Header 컴포넌트 사용

import Header from './components/Header.jsx';

function App() {
  return (
    <>
      <Header />
      {/* 나머지 컴포넌트 */}
    </>
  );
}

export default App;
  • Initial Investment (초기 투자금)
  • Annual Investment (연간 투자금)
  • Expected Return (예상 수익률)
  • Duration (투자 기간)
투자 관련 정보를 입력받기 위한 컴포넌트 만들기

src/components/UserInput.jsx 파일 생성

// UserInput.jsx
export default function UserInput() {
  return (
    <section id="user-input">
      <div className="input-group">
        <p>
          <label>Initial Investment</label>
          <input type="number" required />
        </p>
        <p>
          <label>Annual Investment</label>
          <input type="number" required />
        </p>
      </div>

      <div className="input-group">
        <p>
          <label>Expected Return</label>
          <input type="number" required />
        </p>
        <p>
          <label>Duration</label>
          <input type="number" required />
        </p>
      </div>
    </section>
  );
}

App.jsx에서 UserInput 컴포넌트 사용

import Header from './components/Header.jsx';

function App() {
  return (
    <>
	    <Header />
		<UserInput />
	    {/* 추후 결과 테이블 컴포넌트 추가 예정 */}
    </>
  );
}

export default App;
4개의 투자 관련 입력 필드를 리액트 상태로 관리

상태 초기화

import { useState } from 'react';

const [userInput, setUserInput] = useState({
  initialInvestment: 10000,
  annualInvestment: 1200,
  expectedReturn: 6,
  duration: 10,
});

입력 변경 함수(input 입력받은 속성만 업데이트 불변성 유지)

function handleChange(inputIdentifier, newValue) {
  setUserInput((prevInput) => {
    return {
      ...prevInput,
      [inputIdentifier]: +newValue,  // 문자열을 숫자로 변환
    };
  });
}
각 입력 요소 구성
export default function UserInput({ onChange, userInput }) {
  return (
    <input
      type="number"
      value={userInput.initialInvestment}
      onChange={(e) => onChange('initialInvestment', e.target.value)}
    />
  );
}
  • onChange → 익명 함수로 이벤트 객체 수신
  • 입력 필드의 value는 상태값으로 설정 (양방향 바인딩)
  • 동일한 방식으로 나머지 3개 input도 구성

App.jsx에서 UserInput, Result컴포넌트 연결

<UserInput onChange={handleChange} userInput={userInput} />
<Result input={userInput} />
투자 결과 보여주기

util/investment.js 생성 calculateInvestmentResults(input) 함수를 이용하여 투자 결과 계산

// This function expects a JS object as an argument
// The object should contain the following properties
// - initialInvestment: The initial investment amount
// - annualInvestment: The amount invested every year
// - expectedReturn: The expected (annual) rate of return
// - duration: The investment duration (time frame)
export function calculateInvestmentResults({
  initialInvestment,
  annualInvestment,
  expectedReturn,
  duration,
}) {
  const annualData = [];
  let investmentValue = initialInvestment;

  for (let i = 0; i < duration; i++) {
    const interestEarnedInYear = investmentValue * (expectedReturn / 100);
    investmentValue += interestEarnedInYear + annualInvestment;
    annualData.push({
      year: i + 1, // year identifier
      interest: interestEarnedInYear, // the amount of interest earned in this year
      valueEndOfYear: investmentValue, // investment value at end of year
      annualInvestment: annualInvestment, // investment added in this year
    });
  }

  return annualData;
}

// The browser-provided Intl API is used to prepare a formatter object
// This object offers a "format()" method that can be used to format numbers as currency
// Example Usage: formatter.format(1000) => yields "$1,000"
export const formatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD',
  minimumFractionDigits: 0,
  maximumFractionDigits: 0,
});

src/components/Result.jsx 파일 생성 calculateInvestmentResults(input) 함수로 얻은 결과 데이터를 HTML <table>로 출력

import { calculateInvestmentResults, formatter } from "../util/investment";

export default function Result({ input }) {
  console.log(input);
  const resultsData = calculateInvestmentResults(input);
  console.log(resultsData);
  const initialInvestment =
    resultsData[0].valueEndOfYear -
    resultsData[0].interest -
    resultsData[0].annualInvestment;

  return (
    <table id="result">
      <thead>
        <tr>
          <th>Year</th>
          <th>Investmeth Value</th>
          <th>Interest(Year)</th>
          <th>Total Interest</th>
          <th>Invested Capital</th>
        </tr>
      </thead>
      <tbody>
        {resultsData.map((yearData) => {
          const totalInterest =
            yearData.valueEndOfYear -
            yearData.annualInvestment * yearData.year -
            initialInvestment;
          const totalAmountInvested = yearData.valueEndOfYear - totalInterest;
          return (
            <tr key={yearData.year}>
              <td>{formatter.format(yearData.year)}</td>
              <td>{formatter.format(yearData.valueEndOfYear)}</td>
              <td>{formatter.format(yearData.interest)}</td>
              <td>{formatter.format(totalInterest)}</td>
              <td>{formatter.format(yearData.totalAmountInvested)}</td>
            </tr>
          );
        })}
      </tbody>
    </table>
  );
}

formatter.format()investment.js에서 가져온 포맷터로 금액을 보기 좋게 출력합니다.

마무리 단계 유효성 체크

duration을 0이나 음수로 입력하면 calculateInvestmentResults 함수가 잘못 작동 다른 값은 음수도 허용할 수 있지만, duration반드시 1 이상의 양수여야 의미 있는 결과 도출이 가능 duration 음수로 입력시 에러 메세지 표시

const inputIsValid = userInput.duration >= 1;
{!inputIsValid && (
  <p className="center">Please enter a duration greater than 0.</p>
)}

duration 1보다 작을경우 Please enter a duration greater than 0. 출력

최종 결과물

📑 Reference



Pagination


© 2025. All rights reserved.

Powered by Hydejack v9.2.1