코딩테스트

[해커랭크] CryptoRank ExChange (JS)

여유로운 프론트엔드 개발자 2026. 4. 19. 20:38

해커랭크에 있는 React 문제 중 Data Handling에 관한 문제 CryptoRank ExChange를 풀어보았다.

 

1. 문제 개요

React로 암호화폐 환전 계산기를 만드는 문제이다. 사용자가 USD 금액을 입력하면 각 암호화폐로 몇 개의 코인을 받을 수 있는지 계산해서 테이블에 보여준다.

 

2. 핵심 요구사항

2.1 초기 상태

  • input은 비어있고 에러 메시지 없음
  • Number of Coins 컬럼은 0.00000000 표시

 

2.2 유효성 검사 (3가지 에러)

상황 에러 메시지
input이 비어있을 때 Amount cannot be empty
0.01 미만 입력 Amount cannot be less than $0.01
잔액(17042.67) 초과 Amount cannot exceed the available balance

 

2.3 코인 수 계산

Number of Coins = Amount × Exchange Rate (소수점 8자리)

 

 

2.4 주의해야할 점

  • 처음 렌더링 시에는 에러 없음 → input을 한 번 입력 후 지웠을 때만 "empty" 에러 표시
  • 숫자 에러(음수, 초과)일 때 → n/a
  • empty 에러일 때 → n/a가 아닌 0.00000000 표시

 

3. 구현

 

3.1 상태 설계

const [amount, setAmount] = useState("");
const [touched, setTouched] = useState(false);

amount는 input 값, touched는 사용자가 input을 한 번이라도 조작했는지 여부를 저장한다. touched가 필요한 이유는 초기 렌더링 시 input이 비어있어도 에러를 보여주면 안 되기 때문이다.

 

3.2 유효성 검사

const getError = () => {
  if (amount === "") return touched ? "Amount cannot be empty" : "";
  if (parseFloat(amount) < 0.01) return "Amount cannot be less than $0.01";
  if (parseFloat(amount) > BALANCE) return "Amount cannot exceed the available balance";
  return "";
};
 

3가지 에러를 순서대로 검사한다.

  • amount === "" 일 때 touched가 false면 빈 문자열을 반환해 에러를 숨긴다. 사용자가 값을 입력했다가 지웠을 때(touched === true)만 "Amount cannot be empty"를 반환한다.
  • 0.01 미만이면 최솟값 에러
  • 잔액(17042.67) 초과면 잔액 초과 에러

 

3.3 n/a와 0.00000000 구분

const isEmpty = amount === "";
const validAmount = !error && !isEmpty ? parseFloat(amount) : "";

<Table amount={validAmount} hasError={!!error && !isEmpty} />

에러가 있더라도 empty 에러인지 숫자 에러인지를 구분해야 한다.

상태 hasError amount 코인 표시
초기/empty false "" 0.00000000
음수/초과 true "" 아님 n/a
정상 입력 false 숫자 계산값

!isEmpty를 hasError에 추가해 empty 상태일 때는 hasError=false로 넘긴다.

 

3.4 코인 수 계산

const getCoins = (rate) => {
  if (hasError) return "n/a";
  if (amount === "") return "0.00000000";
  return (amount * rate).toFixed(8);
};
  • hasError가 true면 → "n/a"
  • amount가 비어있으면 → "0.00000000"
  • 정상 입력이면 → amount × rate를 소수점 8자리로 계산

 

3.5 전체 코드

// Main.js
import React, { useState } from "react";
import Table from "./Table";

const BALANCE = 17042.67;

function Main() {
  const [amount, setAmount] = useState("");
  const [touched, setTouched] = useState(false);

  const handleChange = (e) => {
    setTouched(true);
    setAmount(e.target.value);
  };

  const getError = () => {
    if (amount === "") return touched ? "Amount cannot be empty" : "";
    if (parseFloat(amount) < 0.01) return "Amount cannot be less than $0.01";
    if (parseFloat(amount) > BALANCE) return "Amount cannot exceed the available balance";
    return "";
  };

  const error = getError();
  const isEmpty = amount === "";
  const validAmount = !error && !isEmpty ? parseFloat(amount) : "";

  return (
    <div className="layout-column align-items-center mx-auto">
      <h1>CryptoRank Exchange</h1>
      <section>
        <div className="card-text layout-column align-items-center mt-12 px-8 flex text-center">
          <label>
            I want to exchange ${" "}
            <input
              className="w-10"
              data-testid="amount-input"
              required
              type="number"
              placeholder="USD"
              value={amount}
              onChange={handleChange}
            />{" "}
            of my ${BALANCE.toFixed(2)}:
          </label>
          {error && (
            <p data-testid="error" className="form-hint error-text mt-3 pl-0 ml-0">
              {error}
            </p>
          )}
        </div>
      </section>
      <Table amount={validAmount} hasError={!!error && !isEmpty} />
    </div>
  );
}

export default Main;
// Table.js
import React from "react";
import { cryptocurrencyList } from "../cryptocurrency-list";

function Table({ amount, hasError }) {
  const getCoins = (rate) => {
    if (hasError) return "n/a";
    if (amount === "") return "0.00000000";
    return (amount * rate).toFixed(8);
  };

  return (
    <div className="card card-text mt-10 mx-4">
      <table className="mb-0">
        <thead>
          <tr>
            <th>Cryptocurrency</th>
            <th>Exchange Rate</th>
            <th>Number of Coins</th>
          </tr>
        </thead>
        <tbody data-testid="exchange-data">
          {cryptocurrencyList.map((currency) => (
            <tr key={currency.code}>
              <td>{currency.name}</td>
              <td>1 USD = {currency.rate} {currency.code}</td>
              <td>{getCoins(currency.rate)}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

export default Table;