Skip to main content
Version: 5.3

Building a Form

待って、ブラウザを閉じないで!いずれこうなることは分かっていましたよね?そしてもうお気づきでしょうが、Redwoodがフォームをもっと簡単に作る方法を見つけていなければ、このチュートリアルのセクションは存在しなかったのです。 実際、Redwoodはあなたがフォームを作ることを 好き にさせてくれるかもしれません。

まあ、好きというのは強い言葉ですね。フォームを作るのが 好き

フォームを作るのを ガマンできる

私たちのアプリには、すでに1つ2つのフォームがあります。ブログ記事のscaffoldを覚えていますか? そして、それらはかなりうまく機能しています!大変でしたか? (願わくば、そのコードをこっそり見ていないでください -- もし見ていなければ、次に来るものはもっと印象深く感じられるでしょう)。

このブログのために、最もシンプルな "Contact Us"(お問い合わせ) フォームを作ってみましょう。

The Page

yarn rw g page contact

レイアウトのヘッダにお問い合わせへのリンクを設置できます:

web/src/layouts/BlogLayout/BlogLayout.js
import { Link, routes } from '@redwoodjs/router'

const BlogLayout = ({ children }) => {
return (
<>
<header>
<h1>
<Link to={routes.home()}>Redwood Blog</Link>
</h1>
<nav>
<ul>
<li>
<Link to={routes.home()}>Home</Link>
</li>
<li>
<Link to={routes.about()}>About</Link>
</li>
<li>
<Link to={routes.contact()}>Contact</Link>
</li>
</ul>
</nav>
</header>
<main>{children}</main>
</>
)
}

export default BlogLayout

そして BlogLayoutContactPage に使用します。その際、ルーティングファイルの他のページと同じ <Set> でラップすることを確認します:

web/src/Routes.js
import { Router, Route, Set } from '@redwoodjs/router'
import ScaffoldLayout from 'src/layouts/ScaffoldLayout'
import BlogLayout from 'src/layouts/BlogLayout'

const Routes = () => {
return (
<Router>
<Set wrap={ScaffoldLayout} title="Posts" titleTo="posts" buttonLabel="New Post" buttonTo="newPost">
<Route path="/posts/new" page={PostNewPostPage} name="newPost" />
<Route path="/posts/{id:Int}/edit" page={PostEditPostPage} name="editPost" />
<Route path="/posts/{id:Int}" page={PostPostPage} name="post" />
<Route path="/posts" page={PostPostsPage} name="posts" />
</Set>
<Set wrap={BlogLayout}>
<Route path="/article/{id:Int}" page={ArticlePage} name="article" />
<Route path="/contact" page={ContactPage} name="contact" />
<Route path="/about" page={AboutPage} name="about" />
<Route path="/" page={HomePage} name="home" />
</Set>
<Route notfound page={NotFoundPage} />
</Router>
)
}

export default Routes

すべて問題ないことを確認して、いよいよ本題に入りましょう。

Introducing Form Helpers

Reactのフォームは、ややこしいことで有名です。Controlled ComponentsUncontrolled ComponentsThird party libraries など、React のフォームを HTML 仕様で元々意図されていたようにシンプルにするための回避策がたくさんあります:name 属性を持つ <input> フィールドで、ボタンをsubmitするとどこかにデータが送信されます。

Redwoodは、コンポーネントを繋ぎこむコードを書くことから解放されるだけでなく、バリデーションやエラーを自動的に処理することで、正しい方向に一歩も二歩も進んでいると考えています。その仕組みを見てみましょう。

お問い合わせページはデータベースからデータを取得しないのでセルは作りません。ページにフォームを作りましょう。Redwoodのフォームは <Form> タグです:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form } from '@redwoodjs/forms'

const ContactPage = () => {
return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form></Form>
</>
)
}

export default ContactPage

いやー拍子抜けでしたね。ブラウザで見ることもできません。せめて何か見えるように、フォームフィールドを追加してみましょう。Redwoodにはいくつかの入力項目がありますが、プレーンテキストの入力ボックスは <TextField> です。また、フィールドに name 属性を与えて、このページに複数の入力がある場合に、どれがどのデータを含んでいるかがわかるようにします:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form, TextField } from '@redwoodjs/forms'

const ContactPage = () => {
return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form>
<TextField name="input" />
</Form>
</>
)
}

export default ContactPage

ナニカ表示されました!でも、まだかなりつまらない画面です。送信ボタンを追加してみましょうか?

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form, TextField, Submit } from '@redwoodjs/forms'

const ContactPage = () => {
return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form>
<TextField name="input" />
<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

リアルにマジっぽいフォームを用意してみました。何か入力して "Save" (保存)をクリックしてみてください。ページには何も表示されず、フォームが送信されたことも、データがどうなったかもわかりません。次に、フィールドからデータを取得しましょう。

onSubmit

素の HTML フォームと同様に、 <Form>onSubmit ハンドラを渡します。このハンドラは引数として1つのオブジェクト - 送信されたすべてのフォームフィールドを含むオブジェクトを渡して呼び出されます:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form, TextField, Submit } from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<TextField name="input" />
<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

それでは、データを入力して送信し、Webインスペクタ(ブラウザのDeveloper Tools)でコンソールを確認してみてください:

素晴らしい!これをもっと便利なフォームにするために、いくつかのフィールドを追加してみましょう。既存のものを "name" にリネームして、"email" と "message" を追加します:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form, TextField, TextAreaField, Submit } from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<TextField name="name" />
<TextField name="email" />
<TextAreaField name="message" />
<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

新しい <TextAreaField> コンポーネントは HTML の <textarea> を生成しますが、Redwood のフォームの良さを含んでいるので、こちらも見てみてください:

いくつかラベルも追加してみましょう:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import { Form, TextField, TextAreaField, Submit } from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField name="name" />

<label htmlFor="email">Email</label>
<TextField name="email" />

<label htmlFor="message">Message</label>
<TextAreaField name="message" />

<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

フォームに入力して送信してみると、3つのフィールドすべてがコンソールに表示されるはずです。

Validation

あなたはおそらくこう言うでしょう。「よし、Redwoodのチュートリアルの作者さん、あなたはRedwoodのフォームヘルパーを "次の大きな課題" だと言っていますが、フォームを作る手作業を省略できるライブラリはたくさんあります。だからどうしたというのでしょうか?」と。仰るとおり!誰でもフォームに 正しく 入力することができます(ただし、QA担当者はたくさんいますが)、しかし、誰か書き漏らしたり、間違えたり、フォームを不正に操作しようとしたら、どうなるでしょうか?そんなとき、誰が助けてくれるのでしょう?そこでレッドウッドです!

これら3つのフィールドは、誰かが私たちにメッセージを送るために欠かせないものです。標準的なHTMLの required 属性でこれを強制してみましょう:

web/src/pages/ContactPage/ContactPage.js
return (
<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField name="name" required />

<label htmlFor="email">Email</label>
<TextField name="email" required />

<label htmlFor="message">Message</label>
<TextAreaField name="message" required />

<Submit>Save</Submit>
</Form>
)

これで送信しようとすると、ブラウザからフィールドに入力する必要があるというメッセージが表示されます。これは何もしないよりはましですが、これらのメッセージはスタイル付けできません。もっといい方法はないでしょうか?

あります!この required を書き換えて、代わりに Redwood フォームヘルパーの validation というカスタム属性に渡すオブジェクトにしましょう:

web/src/pages/ContactPage/ContactPage.js
return (
<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField name="name" validation={{ required: true }} />

<label htmlFor="email">Email</label>
<TextField name="email" validation={{ required: true }} />

<label htmlFor="message">Message</label>
<TextAreaField name="message" validation={{ required: true }} />

<Submit>Save</Submit>
</Form>
)

そして今、空欄のままフォームを送信すると...Nameフィールドにフォーカスが当たります。つまらんですね。しかし、これは私たちの驚くべき成果への足がかりに過ぎません!エラーを表示するフォームヘルパーコンポーネントをもうひとつ追加します。 お、偶然にもこのコンポーネントは素のHTMLなので、好きなようにスタイルを設定することができます!

<FieldError>

<FieldError> を導入します(冒頭の import ステートメントに含めることを忘れないでください):

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import {
FieldError,
Form,
TextField,
TextAreaField,
Submit,
} from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField name="name" validation={{ required: true }} />
<FieldError name="name" />

<label htmlFor="email">Email</label>
<TextField name="email" validation={{ required: true }} />
<FieldError name="email" />

<label htmlFor="message">Message</label>
<TextAreaField name="message" validation={{ required: true }} />
<FieldError name="message" />

<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

name 属性は、その上の入力フィールドの name と一致することに注意してください。これは、どのフィールドに対してエラーを表示すべきかを知るためです。そのフォームを送信してみてください。

しかし、これはほんの始まりに過ぎません。これがエラーメッセージであることを皆さんに実感してもらいましょう。最初に index.css に追加した基本的なスタイルを覚えていますか?その中に .error クラスがあるので、それを使いましょう。 <FieldError>className 属性を設定します。

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import {
FieldError,
Form,
TextField,
TextAreaField,
Submit,
} from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField name="name" validation={{ required: true }} />
<FieldError name="name" className="error" />

<label htmlFor="email">Email</label>
<TextField name="email" validation={{ required: true }} />
<FieldError name="email" className="error" />

<label htmlFor="message">Message</label>
<TextAreaField name="message" validation={{ required: true }} />
<FieldError name="message" className="error" />

<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

何がいいかって?もし、入力自体になにかしらのエラーがあった場合。入力の errorClassName 属性をチェックしてみてください:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import {
FieldError,
Form,
TextField,
TextAreaField,
Submit,
} from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<label htmlFor="name">Name</label>
<TextField
name="name"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="name" className="error" />

<label htmlFor="email">Email</label>
<TextField
name="email"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="email" className="error" />

<label htmlFor="message">Message</label>
<TextAreaField
name="message"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="message" className="error" />

<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage

さて、label も同様に変更できるとしたらどうでしょう?それは可能ですが、そのためにはRedwoodのカスタムコンポーネントである <Label> が必要になります。他のRedwoodフォームコンポーネントと同様に、 <label>htmlFor 属性が <Label>name propsになることに注意してください。あと import するのを忘れないでください:

web/src/pages/ContactPage/ContactPage.js
import { MetaTags } from '@redwoodjs/web'
import {
FieldError,
Form,
Label,
TextField,
TextAreaField,
Submit,
} from '@redwoodjs/forms'

const ContactPage = () => {
const onSubmit = (data) => {
console.log(data)
}

return (
<>
<MetaTags title="Contact" description="Contact page" />

<Form onSubmit={onSubmit}>
<Label name="name" errorClassName="error">
Name
</Label>
<TextField
name="name"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="name" className="error" />

<Label name="email" errorClassName="error">
Email
</Label>
<TextField
name="email"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="email" className="error" />

<Label name="message" errorClassName="error">
Message
</Label>
<TextAreaField
name="message"
validation={{ required: true }}
errorClassName="error"
/>
<FieldError name="message" className="error" />

<Submit>Save</Submit>
</Form>
</>
)
}

export default ContactPage
Error styling

classNameerrorClassName に加えて、 styleerrorStyle を使用できます。エラーのスタイルについて詳しくは、Form docs を参照してください。

また、エラーとしてマークされたフィールドに何かを入力すると、そのエラーが即座に消えることにもご注目ください!これはユーザにとって、私たちが望むことをやってくれているという素晴らしいフィードバックであり、変更した内容が正しくなったかどうかを確認するためにもう一度 "Save" ボタンをクリックする必要はありません。

Validating Input Format

pattern を指定し、emailフィールドが実際にメールアドレスを含んでいることを確認したいことでしょう。これが完璧なメールアドレスのバリデーションだとは到底言えませんが、今のところは弾除けのようなものだということにしておきましょう。それではメールアドレスの検証のメッセージをもう少しフレンドリーなものに変えてみましょう:

web/src/pages/ContactPage/ContactPage.js
<TextField
name="email"
validation={{
required: true,
pattern: {
value: /^[^@]+@[^.]+\..+$/,
message: 'Please enter a valid email address',
},
}}
errorClassName="error"
/>
info

バリデーションエラーが表示された場合、フィールドの内容を修正するとすぐに 消え ます。エラーメッセージを消すためにもう一度 "送信" する必要はありません。これは、ユーザ(および鋭い目を持つQAテスタ)にとって、変更した内容が正しくなったというフィードバックを即座に受けることができるため、素晴らしいフィードバックとなります。

最後に、何が 本当に 素晴らしいか分かりますか?ユーザがフィールドから離れるとすぐにフィールドが検証されれば、ユーザがすべてを記入して送信したら複数のエラーが表示されるという事態を避けることができます。そうしましょう:

web/src/pages/ContactPage/ContactPage.js
<Form onSubmit={onSubmit} config={{ mode: 'onBlur' }}>

さて、あなたはどう思いますか?大げさに宣伝した価値はあったでしょうか?いくつかの新しいコンポーネントで、バリデーションを処理し、送信された値をいい感じのデータオブジェクトにラップするフォームを、すべて無料で手に入れることができます。

info

Redwood のフォームは React Hook Form の上に構築されているので、ここで説明したよりもさらに多くの機能が利用できます。フォームの機能については Form docs を参照してください。

Redwoodにはもう一つ、フォームに関するトリックがありますが、そのあたりは実際にサーバにフォームを送信するときのためにとっておきましょう。

お問い合わせフォームを持つことは素晴らしいことですが、それは実際に何らかの形でお問い合わせを受ける場合だけです。送信されたデータを保持するデータベーステーブルを作成し、最初のGraphQLミューテーションを作成しましょう。