---
# System prepended metadata

title: 卡比 React 讀書會 | Sign up form
tags: [React, ' 讀書會']

---

---
title: 卡比 React 讀書會 | Sign up form
tags: React, 讀書會
---

# 卡比 React 讀書會 | Sign up form | week 1

## Sign up form

- [設計稿連結 - 請自行下載!](https://www.frontendmentor.io/challenges/intro-component-with-signup-form-5cf91bd49edda32581d28fd1)
- [卡比直播連結](https://www.youtube.com/watch?v=IfaTpyhaGlg)
- Source code 自己去找卡比的 Github 找到算你贏XD

### 使用技術

- React
- Tailwindcss
- Vite


### Step 1 - Tailwind config 設定

依照設計稿提供的 Style guide 配置，盡量放在 root 可以讓後續維護的人容易理解

- 主題色
- 字體
- 背景

#### `./tailwind.config.js`
```javascript=
module.exports = {
  mode: "jit",
  purge: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {
      colors: {
        red: `#FF7979`,
        green: `#38CC8B`,
        blue: {
          DEFAULT: `#5E54A4`,
          dark: `#3D3B48`,
        },
        gray: "#BAB7D4",
      },

      boxShadow: {
        DEFAULT: `0px 8px 0px rgba(0, 0, 0, 0.14688)`,
        solid: `inset 0px -4px 0px rgba(0, 0, 0, 0.0908818)`,
      },
    },
  },
  variants: {
    extend: {},
  },
  plugins: [],
};
```

#### `./src/style.css`
```css=
@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@100;200;300;400;500;600;700;800;900&display=swap");

@tailwind base;
@tailwind components;
@tailwind utilities;

html,
body {
  font-family: "Poppins", sans-serif;

  @apply bg-red h-screen;
}

#root {
  @apply h-screen;
  background: url("./images/bg-intro-mobile.png");
}

@media (min-width: 640px) {
  #root {
    background: url("./images/bg-intro-desktop.png");
  }
}
```

### Step 2 - 開切 畫面切版

- 小叮嚀
    - About Layout(讓我想想)
        1. 盡量使用語意化的 HTML 標籤 (eg: `article`, `p`, `b`, `form`, `label`...etc)，不要甚麼都用 `div`
        2. 從手機版開始設計~
        3. CSS style 會像像瀑布一樣，樣式會往下一直跑，不需要每個標籤都設定，找到共同的父元素來設定即可。
            - (CSS: Cascading Style Sheets)[https://developer.mozilla.org/en-US/docs/Web/CSS]
        4. placeholder 讀字機讀不到，加上 label 的 form 才會比較符合無障礙 
        5. [Form 的相關規範](https://www.w3.org/WAI/tutorials/forms/)
    - About React
        1. 元件可以保持沒有邏輯，有商業邏輯的部分拉到主要頁面上，方便後續管理 

- 切元件 - 試著邊切版邊處理元件，不用回頭來處理
    - 需要注意!
        - React 中的元件若有任何部分要更新，就會整個重新渲染，所以切得細一點將可以提升效能。
    - 用你銳利的眼睛來觀察設計稿哪邊有共用的 style，不要重複做同樣的事情
    - 共用元件用 `props` 來從父層傳入內容
    - 使用某種方式 (請注意 `function clsx(){}` 的使用) 來增加元件的 style 彈性，這樣可以輕鬆的設定好預設的 class，並能在父元件使用元件時使用不同的 class 來製作不同的元件外觀
        - ![](https://i.imgur.com/v34BZ74.png)
    - `useState` 狀態機
        - 建議初始狀態不要用 `undefined`
        - eg: `const [_value, setValue] = useState("");`
- ![](https://i.imgur.com/v2V32da.png)

- 表單的 input 元件

```jsx=
function clsx(...str) {
  return str.filter(Boolean).join(" ");
}

function TextField({ id, label }) {
  const [value, setValue] = useState("");

  return (
    <div className="relative flex items-center">
      <label
        htmlFor={id}
        className={clsx(
          "absolute px-3",
          "text-sm font-medium",
          value !== "" && "opacity-0"
        )}
      >
        {label}
      </label>

      <input
        type="text"
        name={id}
        id={id}
        className={clsx(
          "outline-none",
          "border border-opacity-10 w-full p-3 rounded",
          "border-blue-dark focus:border-blue"
        )}
        onChange={(event) => setValue(event.target.value)}
      />
    </div>
  );
}
```

### Step 3 Form's error state
1. Form submit 的時候取消預設的瀏覽器事件。

```jsx=
function onSubmit(event) {
    event.preventDefault();
}
```

2. 使用 formData 來取得 Form 裡面的值

```diff=
function onSubmit(event) {
    event.preventDefault();
+    const form = new FormData(event.target);
+    const data = Object.fromEntries(form.entries());
+    console.log(data);
}
```

3. 跑迴圈來取得每一個 input 是否有值，並回傳 Boolean 值

```diff=
function onSubmit(event) {
    event.preventDefault();

    const form = new FormData(event.target);
-    const data = Object.fromEntries(form.entries());
-    console.log(data);

+    for (const [key, value] of form.entries()) {
+      const hasFilled = Boolean(value);
+      console.log(key, hasFilled);
+    }
 }
```


4. 使用 Hook 來改變 FormState

```diff
function onSubmit(event) {
    event.preventDefault();

    const form = new FormData(event.target);

    const data = Object.fromEntries(form.entries());

-    for (const [key, value] of form.entries()) {
-      const hasFilled = Boolean(value);
-      console.log(key, hasFilled);
-    }
+    setFormState((formstate) =>
+      formstate.map((state) => ({
+        ...state,
+        error: !Boolean(data[state.id]),
+      }))
+    );
 }
```

4. 處理錯誤時出現的 SVG 
    - 先處理好 svg 檔
        - 拿掉 svg 標籤的 width & height & fill 改成 currentColor，就可以用程式改變顏色與寬高 
    - SVG import 成一個元件
        - [vite-plugin-svgr](https://github.com/pd4d10/vite-plugin-svgr)

5. TADA!

```jsx=
function TextField({ id, label, error }) {
  const [value, setValue] = useState("");

  return (
    <div>
      <div className={clsx("relative flex items-center", error && "text-red")}>
        <label
          htmlFor={id}
          className={clsx(
            "absolute px-3",
            "text-sm font-medium",
            value !== "" && "opacity-0"
          )}
        >
          {label}
        </label>

        <input
          type="text"
          name={id}
          id={id}
          className={clsx(
            "outline-none",
            "border w-full p-3 rounded",
            error
              ? "border-red"
              : "border-blue-dark focus:border-blue border-opacity-10"
          )}
          onChange={(event) => setValue(event.target.value)}
        />

        {error && (
          <ErrorIcon className="absolute right-0 py-3 mr-3 h-full w-6" />
        )}
      </div>

      {error && (
        <div className="flex justify-end">
          <span className="text-xs text-red">{error}</span>
        </div>
      )}
    </div>
  );
}
```

### Step 4 Deployment

- 使用 Vercel，請看影片 1:55:44 左右
- [卡比部屬給你看](https://youtu.be/IfaTpyhaGlg?t=6944)

> 波特媽: 要不要試試看vercel? 點點點擺著等等等就部署好了