Reactアプリ100本ノックを実践する 〜05 Calc〜

# はじめに
こちらの記事は、@Sicut_studyさんがアップしている【Reactアプリ100本ノック】シリーズに相乗りし、アウトプットを行うための記事になります。
- 実装ルールや成果物の達成条件は元記事に従うものとします。
- 元記事との差別点として、具体的に自分がどんな実装を行ったのか（と必要に応じて解説）を記載します。

@Sicut_studyさんのノック100本についていき、Reactを100日間学ぶのが目標です。

今回の元記事はこちら

https://qiita.com/Sicut_study/items/c38b51907d18131b31f6

# 前回の記事

https://qiita.com/jinto/items/1d3deca4939de3528e2d

# 問題
簡易的な電卓を作成する

## ルール
**元記事より引用**
- 主要なライブラリやフレームワークはReactである必要がありますが、その他のツールやライブラリ（例: Redux, Next.js, Styled Componentsなど）を組み合わせて使用することは自由
- TypeScriptを利用する
- 要件をみたせばデザインなどは自由

## 達成条件
**元記事より引用**
- 四則演算（加算、減算、乗算、除算）が正確に行えること。
- 数字と演算子の入力が可能で、計算結果が表示されること。
- クリアボタンで入力と結果をリセットできること。
- 連続した数字入力など計算ができないときはエラーを表示する

# 実装

本記事では以下の方針で実装していきます。
- ボタンをコンポーネント化する
- 小数点の計算も可能とする
- 計算機能の実装には`Math.js`を活用する

コードは以下のようになりました。

```typescript:src/components/Button.tsx
/** @jsxImportSource @emotion/react */
import { css } from "@emotion/react";

const buttonStyle = css`
  flex: 1;
  padding: 15px;
  margin: 5px;
  font-size: 18px;
  border-radius: 8px;
  background-color: #61dafb;
  color: #282c34;
  border: none;
  cursor: pointer;
  &:hover {
    background-color: #51cfeb;
  }
`;

interface ButtonProps {
  onClick: React.MouseEventHandler<HTMLButtonElement>;
  children: React.ReactNode;
}

const Button = ({ onClick, children }: ButtonProps) => (
  <button css={buttonStyle} onClick={onClick}>
    {children}
  </button>
);

export default Button;
```

```typescript:src/App.tsx
/** @jsxImportSource @emotion/react */
import { useState } from "react";
import { css } from "@emotion/react";
import { evaluate } from "mathjs";
import Button from "./components/Button";

const containerStyle = css`
  display: flex;
  flex-direction: column;
  align-items: center;
  background-color: #282c34;
  min-height: 100vh;
  color: white;
  font-family: 'Arial', sans-serif;
`;

const screenStyle = css`
  width: 300px;
  background: #fff;
  color: #333;
  margin-top: 10px;
  margin-bottom: 10px;
  text-align: right;
  padding: 20px;
  font-size: 24px;
  border-radius: 8px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
`;

const buttonRowStyle = css`
  display: flex;
  justify-content: space-between;
  width: 300px;
`;

const App = () => {
  const [input, setInput] = useState("");
  const [result, setResult] = useState("");

  const handleButtonClick = (val: string) => {
    setInput((prev) => prev + val);
  };

  const calculateResult = () => {
    try {
      const result = evaluate(input);
      setInput(result.toString());
      setResult("");
    } catch (error) {
      setResult("Error");
      setInput("");
    }
  };

  const clearInput = () => {
    setInput("");
    setResult("");
  };

  return (
    <div css={containerStyle}>
      <div css={screenStyle}>{input || "0"}</div>
      <div css={buttonRowStyle}>
        <Button onClick={() => handleButtonClick("1")}>1</Button>
        <Button onClick={() => handleButtonClick("2")}>2</Button>
        <Button onClick={() => handleButtonClick("3")}>3</Button>
        <Button onClick={() => handleButtonClick("+")}>+</Button>
      </div>
      <div css={buttonRowStyle}>
        <Button onClick={() => handleButtonClick("4")}>4</Button>
        <Button onClick={() => handleButtonClick("5")}>5</Button>
        <Button onClick={() => handleButtonClick("6")}>6</Button>
        <Button onClick={() => handleButtonClick("-")}>-</Button>
      </div>
      <div css={buttonRowStyle}>
        <Button onClick={() => handleButtonClick("7")}>7</Button>
        <Button onClick={() => handleButtonClick("8")}>8</Button>
        <Button onClick={() => handleButtonClick("9")}>9</Button>
        <Button onClick={() => handleButtonClick("*")}>*</Button>
      </div>
      <div css={buttonRowStyle}>
        <Button onClick={clearInput}>C</Button>
        <Button onClick={() => handleButtonClick("0")}>0</Button>
        <Button onClick={() => handleButtonClick(".")}>.</Button>
        <Button onClick={() => handleButtonClick("/")}>/</Button>
      </div>
      <div css={buttonRowStyle}>
        <Button onClick={calculateResult}>=</Button>
      </div>
    </div>
  );
};

export default App;
```

# 完成
完成形はこのようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267522/ee2398c3-b795-1879-f8f3-a0efa792d71b.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267522/8c9919f8-6cea-5da1-cf1b-97a85674a72a.png)

# 最後に
まだまだ先は長いですが、100回完走を目指して継続していきます。
応援してくれる方はぜひフォローいただけると嬉しいです。
いいね、ストックもお待ちしております。

ではまた。

# 次回の記事

https://qiita.com/jinto/items/5610db3c6d75847d3aae