---
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 來製作不同的元件外觀
- 
- `useState` 狀態機
- 建議初始狀態不要用 `undefined`
- eg: `const [_value, setValue] = useState("");`
- 
- 表單的 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? 點點點擺著等等等就部署好了