0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Shadocn/ui】React、React-hook-formで入力フォームを作ってみる

Last updated at Posted at 2025-10-25

Shadcn/uiを使った入力フォームを作っていきます。

image.png

ディレクトリ構成

└── ReactApp/
    ├── node-modules
    ├── public
    └── src/
        ├── assets
        ├── components\ui
        ├── lib
        ├── App.tsx
        ├── Form.tsx
        ├── index.css
        └── ...(略)

npmを使ったパッケージ導入

Shadcn/uiの各コンポーネントの導入

Shadcn/uiの各コンポーネントをパッケージ管理ツールnpmを使ってインストールしていきます。

●Buttonコンポーネント

npx shadcn@latest add button

●Cardコンポーネント

npx shadcn@latest add card

●Fieldコンポーネント

npx shadcn@latest add field

●Inputコンポーネント

npx shadcn@latest add input

●Input-Groupコンポーネント

npx shadcn@latest add input-group

hookform/resolversの導入

react-hook-formの導入

sonnerの導入

zodの導入

コード

src/App.tsx
import React,{ useState } from 'react';
import Login from './Login';
import Register from './Register';
import axios from 'axios';
import { BugReportForm } from './Form';

const App = ()=>{
    return (
    <div className="flex justify-center items-center min-h-screen p-8 bg-gray-50">
      <BugReportForm />
    </div>
  )
}

export default App;
src/form.tsx
import * as React from "react"
import { zodResolver } from "@hookform/resolvers/zod"
import { Controller, useForm } from "react-hook-form"
import { toast } from "sonner"
import * as z from "zod"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  Field,
  FieldDescription,
  FieldError,
  FieldGroup,
  FieldLabel,
} from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import {
  InputGroup,
  InputGroupAddon,
  InputGroupText,
  InputGroupTextarea,
} from "@/components/ui/input-group"

const formSchema = z.object({
  title: z
    .string()
    .min(5, "Titleは、少なくとも5文字以上入力してください。")
    .max(32, "Titleは、最大32文字以下入力してください。"),
  description: z
    .string()
    .min(20, "アンケート内容欄は、少なくとも20文字以上入力してください。")
    .max(100, "アンケート内容欄は、最大100文字以下入力してください。"),
})

export function BugReportForm() {
  const form = useForm<z.infer<typeof formSchema>>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      title: "",
      description: "",
    },
  })

  function onSubmit(data: z.infer<typeof formSchema>) {
    toast("You submitted the following values:", {
      description: (
        <pre className="bg-code text-code-foreground mt-2 w-[320px] overflow-x-auto rounded-md p-4">
          <code>{JSON.stringify(data, null, 2)}</code>
        </pre>
      ),
      position: "bottom-right",
      classNames: {
        content: "flex flex-col gap-2",
      },
      style: {
        "--border-radius": "calc(var(--radius)  + 4px)",
      } as React.CSSProperties,
    })
  }

  return (
    <Card className="w-full sm:max-w-md">
      <CardHeader>
        <CardTitle>アンケート</CardTitle>
        <CardDescription>
          アンケートの入力をお願いいたします。
        </CardDescription>
      </CardHeader>
      <CardContent>
        <form id="form-rhf-demo" onSubmit={form.handleSubmit(onSubmit)}>
          <FieldGroup>
            <Controller
              name="title"
              control={form.control}
              render={({ field, fieldState }) => (
                <Field data-invalid={fieldState.invalid}>
                  <FieldLabel htmlFor="form-rhf-demo-title">
                    タイトル                  
                  </FieldLabel>
                  <Input
                    {...field}
                    id="form-rhf-demo-title"
                    aria-invalid={fieldState.invalid}
                    placeholder="タイトルを入力"
                    autoComplete="off"
                  />
                  {fieldState.invalid && (
                    <FieldError errors={[fieldState.error]} />
                  )}
                </Field>
              )}
            />
            <Controller
              name="description"
              control={form.control}
              render={({ field, fieldState }) => (
                <Field data-invalid={fieldState.invalid}>
                  <FieldLabel htmlFor="form-rhf-demo-description">
                    アンケート内容
                  </FieldLabel>
                  <InputGroup>
                    <InputGroupTextarea
                      {...field}
                      id="form-rhf-demo-description"
                      placeholder="アンケート内容を入力"
                      rows={6}
                      className="min-h-24 resize-none"
                      aria-invalid={fieldState.invalid}
                    />
                    <InputGroupAddon align="block-end">
                      <InputGroupText className="tabular-nums">
                        {field.value.length}/100 characters
                      </InputGroupText>
                    </InputGroupAddon>
                  </InputGroup>
                  <FieldDescription>
                    アンケートにご協力いただきありがとうございます。
                  </FieldDescription>
                  {fieldState.invalid && (
                    <FieldError errors={[fieldState.error]} />
                  )}
                </Field>
              )}
            />
          </FieldGroup>
        </form>
      </CardContent>
      <CardFooter>
        <Field orientation="horizontal">
          <Button type="button" variant="outline" onClick={() => form.reset()}>
            Reset
          </Button>
          <Button type="submit" form="form-rhf-demo">
            Submit
          </Button>
        </Field>
      </CardFooter>
    </Card>
  )
}

サイト

■Shadcn/ui

■npm公式サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?