React 筆記

記錄一下常用到的 Components

JSX

shadcn

相見恨晚阿.. 太強了
官方文件

TanStack

官方有個非常詳盡的文件,這裡只有寫大概怎麼用。

文件寫到 TanStack Table is a modular library. Not all code for every single feature is included in the createTable functions/hooks by default.,所以很多功能要在建立 table 的時候自己 import。

  • 安裝

    1
    npm install @tanstack/react-table

Header Group 包含所有 header row

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<thead>
{table.getHeaderGroups().map((headerGroup) => {
return (
<tr key={headerGroup.id}>
{headerGroup.headers.map(
(
header // map over the headerGroup headers array
) => (
<th key={header.id} colSpan={header.colSpan}>
{/* */}
</th>
)
)}
</tr>
);
})}
</thead>

Column

可以從 cellheadertable 取得

1
2
3
const column = cell.column; // get column from cell
const column = header.column; // get column from header
const column = table.getColumn("firstName");

Row

全部可以用的 row model

1
2
3
4
5
6
7
8
9
10
11
12
13
const table = useReactTable({
columns,
data,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
getFacetedMinMaxValues: getFacetedMinMaxValues(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFilteredRowModel: getFilteredRowModel(),
getGroupedRowModel: getGroupedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
});

table.getRow()

1
const row = table.getRow(rowId);

table.getRowModel()

1
2
3
4
5
<tbody>
{table.getRowModel().rows.map((row) => (
<tr key={row.id}>{/* ... */}</tr>
))}
</tbody>

table.getSelectedRowModel()

1
const selectedRows = table.getSelectedRowModel().rows;

row.getValue()row.renderValue()

兩個只差在檢查 value 是否為 undefined

1
2
const firstName = row.getValue("firstName"); // read the row value from the firstName column
const renderedLastName = row.renderValue("lastName"); // render the value from the lastName column

row.original

row.getValue() 類似,但是回傳 accessorFn 處理前 的值,是最原始的資料

1
const firstName = row.original.firstName;

Cell

每個 Cell 的 id 是由 parent row 和 parent col 決定的:{ id: ${row.id}_${column.id} }

cell.getValue()cell.renderValue()

1
2
const firstName = cell.getValue(); // read the cell value
const renderedLastName = cell.renderValue(); // render the value

取得 parent row

1
const firstName = cell.row.original.firstName;

flexRender()

如果 column.cell函數回傳 JSX 而不是純數據,就一定要用 flexRender 渲染,不能用 getValue()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { flexRender } from '@tanstack/react-table'

const columns = [
{
accessorKey: 'fullName',
cell: ({ cell, row }) => {
return <div><strong>{row.original.firstName}</strong> {row.original.lastName}</div>
}
//...
}
]
//...
<tr>
{row.getVisibleCells().map(cell => {
return <td key={cell.id}>{flexRender(cell.column.columnDef.cell, cell.getContext())}</td>
})}
</tr>
getVisibleCells()

只回傳當前可見的 Cells,避免 render 不需要的內容

1
2
3
4
5
6
7
8
9
{
table.getRowModel().rows.map((row) => (
<tr key={row.id}>
{row.getVisibleCells().map((cell) => (
<td key={cell.id}>{cell.renderValue()}</td>
))}
</tr>
));
}

React Hook Form

透過 useForm hook 管理表單

  • 安裝

    1
    npm install react-hook-form
  • shadui + react hook form + zod 驗證範例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    import { useForm } from "react-hook-form";
    import { zodResolver } from "@hookform/resolvers/zod";
    import * as z from "zod";
    import { Button } from "@/components/ui/button";
    import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
    import { Input } from "@/components/ui/input";
    import { Checkbox } from "@/components/ui/checkbox";

    const formSchema = z.object({
    name: z.string().min(2, "名稱至少需要 2 個字"),
    email: z.string().email("請輸入有效的 Email"),
    agreeTerms: z.boolean().refine(val => val === true, {
    message: "您必須同意條款與條件"
    })
    });

    export default function MyForm() {
    const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
    name: "",
    email: "",
    agreeTerms: false
    }
    });

    const onSubmit = (data) => {
    console.log("Submitted data:", data);
    };

    return (
    <Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 p-4 border rounded-lg w-full max-w-md">
    {/* Name Field */}
    <FormField
    control={form.control}
    name="name"
    render={({ field }) => (
    <FormItem>
    <FormLabel>名稱</FormLabel>
    <FormControl>
    <Input placeholder="輸入您的名稱" {...field} />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Email Field */}
    <FormField
    control={form.control}
    name="email"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Email</FormLabel>
    <FormControl>
    <Input type="email" placeholder="輸入您的 Email" {...field} />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Agree Terms Checkbox */}
    <FormField
    control={form.control}
    name="agreeTerms"
    render={({ field }) => (
    <FormItem className="flex items-center space-x-2">
    <FormControl>
    <Checkbox checked={field.value} onCheckedChange={field.onChange} />
    </FormControl>
    <FormLabel>我同意條款與條件</FormLabel>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Submit Button */}
    <Button type="submit" className="w-full">提交</Button>
    </form>
    </Form>
    );
    }
    import { useForm } from "react-hook-form";
    import { zodResolver } from "@hookform/resolvers/zod";
    import * as z from "zod";
    import { Button } from "@/components/ui/button";
    import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from "@/components/ui/form";
    import { Input } from "@/components/ui/input";
    import { Checkbox } from "@/components/ui/checkbox";

    const formSchema = z.object({
    name: z.string().min(2, "名稱至少需要 2 個字"),
    email: z.string().email("請輸入有效的 Email"),
    agreeTerms: z.boolean().refine(val => val === true, {
    message: "您必須同意條款與條件"
    })
    });

    export default function MyForm() {
    const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
    name: "",
    email: "",
    agreeTerms: false
    }
    });

    const onSubmit = (data) => {
    console.log("Submitted data:", data);
    };

    return (
    <Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4 p-4 border rounded-lg w-full max-w-md">
    {/* Name Field */}
    <FormField
    control={form.control}
    name="name"
    render={({ field }) => (
    <FormItem>
    <FormLabel>名稱</FormLabel>
    <FormControl>
    <Input placeholder="輸入您的名稱" {...field} />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Email Field */}
    <FormField
    control={form.control}
    name="email"
    render={({ field }) => (
    <FormItem>
    <FormLabel>Email</FormLabel>
    <FormControl>
    <Input type="email" placeholder="輸入您的 Email" {...field} />
    </FormControl>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Agree Terms Checkbox */}
    <FormField
    control={form.control}
    name="agreeTerms"
    render={({ field }) => (
    <FormItem className="flex items-center space-x-2">
    <FormControl>
    <Checkbox checked={field.value} onCheckedChange={field.onChange} />
    </FormControl>
    <FormLabel>我同意條款與條件</FormLabel>
    <FormMessage />
    </FormItem>
    )}
    />

    {/* Submit Button */}
    <Button type="submit" className="w-full">提交</Button>
    </form>
    </Form>
    );
    }

Swiper

  • 安裝

    1
    npm install swiper
  • import

    • 一定要的

      1
      2
      import { Swiper, SwiperSlide } from "swiper/react";
      import "swiper/css";
    • 其他一些可用的 modules

      1
      2
      import { EffectCoverflow, Pagination } from "swiper/modules";
      import "swiper/css/pagination";
  • 用法範例

    • Swiper
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      <Swiper
      onSlideChange={(swiper) => setcurrentAppIntroIndex(swiper.activeIndex)}
      initialSlide={2}
      slidesPerView={"auto"}
      coverflowEffect={{
      rotate: 0,
      stretch: 80,
      depth: 300,
      modifier: 1,
      slideShadows: true,
      }}
      effect={"coverflow"}
      grabCursor={true}
      centeredSlides={true}
      onSwiper={(swiper) => (appSwiperRef.current = swiper)}
      pagination={{ el: ".app-pagination", clickable: true }}
      modules={[EffectCoverflow, Pagination]}
      >
      {appFeatures.map((item, index) => (
      <SwiperSlide key={index}>
      <div>{item.title}</div>
      </SwiperSlide>
      ))}
      </Swiper>
    • Pagination
      1
      2
      3
      4
      5
      6
      7
      8
      9
      <div
      className="app-pagination mt-2 flex justify-center space-x-4"
      style={{
      "--swiper-pagination-color": "#000000",
      "--swiper-pagination-bullet-inactive-color": "#999999",
      "--swiper-pagination-bullet-inactive-opacity": "1",
      "--swiper-pagination-bullet-size": "8px",
      }}
      />

Collapse

  • 安裝

    1
    npm install --save react-collapse
  • import

    1
    import { Collapse } from "react-collapse";

    另外,index.html 要加入

    1
    2
    <script src="https://unpkg.com/react/umd/react.production.min.js"></script>
    <script src="https://unpkg.com/react-collapse/build/react-collapse.min.js"></script>
  • 用法範例
    isOpened 控制 Toggle 就好,內容隨意

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <Collapse isOpened={isExpanded[index]}>
    {question.description &&
    question.description.map((desc, index) => (
    <div>
    <div key={index}>{desc.description}</div>
    {desc.details && (
    <ul className="ml-10 list-disc">
    {desc.details.map((detail, index) => (
    <li key={index}>
    <span className="font-bold">{detail.title}</span>
    {detail.description}
    </li>
    ))}
    </ul>
    )}
    </div>
    ))}
    </Collapse>
    • Collapse 動畫的時間
    1
    2
    3
    .ReactCollapse--collapse {
    transition: height 500ms;
    }

Select

  • 安裝

    1
    npm i --save react-select
  • import

    1
    import Select from "react-select";
  • 範例

    1
    2
    3
    4
    5
    6
    const options = [
    { value: "chocolate", label: "Chocolate" },
    { value: "strawberry", label: "Strawberry" },
    { value: "vanilla", label: "Vanilla" },
    ];
    const MyComponent = () => <Select options={options} />;
  • 更多用法

Spinner

  • 安裝

    1
    npm install --save react-spinners
  • import

    1
    import ClipLoader from "react-spinners/ClipLoader";
  • 範例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <ClipLoader
    color="#ffffff"
    loading={loading} // bool 值
    size={150}
    aria-label="Loading Spinner"
    data-testid="loader"
    />

    <BeatLoader
    color="#000000"
    loading={true} // bool
    size={10}
    aria-label="Loading Spinner"
    data-testid="loader"
    />
  • 更多用法

CSS

flexwrap

flex 的時候,會與下一行有一些奇怪的間隔,是因為他會啟動 aligin-content: strench,要改成 align-content: flex-start,或 Tailwind 的 content-start


React 筆記
https://933yee.github.io/notes/2025/02/05/react-note/
Author
Kevin Lee
Posted on
February 5, 2025
Licensed under